Merge pull request #1983 from odoo-dev/master-pos-new-payment-screen-fva
Point of Sale : New Payment Screen & Receipt Screens
This commit is contained in:
@@ -72,6 +72,7 @@ class pos_config(osv.osv):
|
||||
'iface_scan_via_proxy' : fields.boolean('Scan via Proxy', help="Enable barcode scanning with a remotely connected barcode scanner"),
|
||||
'iface_invoicing': fields.boolean('Invoicing',help='Enables invoice generation from the Point of Sale'),
|
||||
'iface_big_scrollbars': fields.boolean('Large Scrollbars',help='For imprecise industrial touchscreens'),
|
||||
'iface_fullscreen': fields.boolean('Fullscreen', help='Display the Point of Sale in full screen mode'),
|
||||
'receipt_header': fields.text('Receipt Header',help="A short text that will be inserted as a header in the printed receipt"),
|
||||
'receipt_footer': fields.text('Receipt Footer',help="A short text that will be inserted as a footer in the printed receipt"),
|
||||
'proxy_ip': fields.char('IP Address', help='The hostname or ip address of the hardware proxy, Will be autodetected if left empty', size=45),
|
||||
@@ -394,7 +395,7 @@ class pos_session(osv.osv):
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
for statement in obj.statement_ids:
|
||||
statement.unlink(context=context)
|
||||
return True
|
||||
return super(pos_session, self).unlink(cr, uid, ids, context=context)
|
||||
|
||||
|
||||
def open_cb(self, cr, uid, ids, context=None):
|
||||
@@ -553,6 +554,7 @@ class pos_order(osv.osv):
|
||||
orders_to_save = [o for o in orders if o['data']['name'] not in existing_references]
|
||||
|
||||
order_ids = []
|
||||
|
||||
for tmp_order in orders_to_save:
|
||||
to_invoice = tmp_order['to_invoice']
|
||||
order = tmp_order['data']
|
||||
|
||||
@@ -628,57 +628,80 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name"/>
|
||||
</h1>
|
||||
<group col="4">
|
||||
<field name="picking_type_id" widget="selection" groups="stock.group_locations"
|
||||
on_change="onchange_picking_type_id(picking_type_id)"/>
|
||||
<field name="stock_location_id" groups="stock.group_locations"/>
|
||||
<field name="pricelist_id" groups="product.group_sale_pricelist"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="journal_id" widget="selection"/>
|
||||
<field name="group_by" groups="account.group_account_user"/>
|
||||
<field name="sequence_id" readonly="1" groups="base.group_no_one"/>
|
||||
</group>
|
||||
<separator string="Available Payment Methods" colspan="4"/>
|
||||
<field name="journal_ids" colspan="4" nolabel="1">
|
||||
<tree string="Journals">
|
||||
<field name="code" />
|
||||
<field name="name" />
|
||||
<field name="type" />
|
||||
<field name="cash_control" />
|
||||
</tree>
|
||||
</field>
|
||||
<group string="Features" >
|
||||
<group>
|
||||
<field name="iface_vkeyboard" />
|
||||
<field name="iface_invoicing" />
|
||||
<sheet>
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name"/>
|
||||
</h1>
|
||||
<group col="4">
|
||||
<field name="picking_type_id" widget="selection" groups="stock.group_locations"
|
||||
on_change="onchange_picking_type_id(picking_type_id)"/>
|
||||
<field name="stock_location_id" groups="stock.group_locations"/>
|
||||
<field name="pricelist_id" groups="product.group_sale_pricelist"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="journal_id" widget="selection"/>
|
||||
<field name="group_by" groups="account.group_account_user"/>
|
||||
<field name="sequence_id" readonly="1" groups="base.group_no_one"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="iface_big_scrollbars" />
|
||||
<separator string="Available Payment Methods" colspan="4"/>
|
||||
<field name="journal_ids" colspan="4" nolabel="1">
|
||||
<tree string="Journals">
|
||||
<field name="code" />
|
||||
<field name="name" />
|
||||
<field name="type" />
|
||||
<field name="cash_control" />
|
||||
</tree>
|
||||
</field>
|
||||
<group string="Features" >
|
||||
<group>
|
||||
<field name="iface_vkeyboard" />
|
||||
<field name="iface_invoicing" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="iface_fullscreen" />
|
||||
<field name="iface_big_scrollbars" />
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Hardware Proxy" >
|
||||
<field name="proxy_ip" />
|
||||
<field name="iface_print_via_proxy" />
|
||||
<field name="iface_scan_via_proxy" />
|
||||
<field name="iface_electronic_scale" />
|
||||
<field name="iface_cashdrawer" />
|
||||
</group>
|
||||
<group string="Receipt" >
|
||||
<field name="receipt_header" placeholder="A custom receipt header message"/>
|
||||
<field name="receipt_footer" placeholder="A custom receipt header footage"/>
|
||||
</group>
|
||||
<group string="Barcode Types" col="4">
|
||||
<field name="barcode_product" />
|
||||
<field name="barcode_cashier" />
|
||||
<field name="barcode_customer" />
|
||||
<field name="barcode_weight" />
|
||||
<field name="barcode_discount" />
|
||||
<field name="barcode_price" />
|
||||
</group>
|
||||
<group string="Hardware Proxy" >
|
||||
<field name="proxy_ip" />
|
||||
<field name="iface_print_via_proxy" />
|
||||
<field name="iface_scan_via_proxy" />
|
||||
<field name="iface_electronic_scale" />
|
||||
<field name="iface_cashdrawer" />
|
||||
</group>
|
||||
<group string="Receipt" >
|
||||
<field name="receipt_header" placeholder="A custom receipt header message"/>
|
||||
<field name="receipt_footer" placeholder="A custom receipt header footage"/>
|
||||
</group>
|
||||
<group string="Barcode Types" col="1">
|
||||
<p>
|
||||
Barcode Patterns allow to match barcodes to actions or to embed information such as price and quantity in the barcode.
|
||||
Barcode Patterns only work with EAN13 barcodes.
|
||||
</p>
|
||||
<p>
|
||||
Each type of barcode accepts a list of patterns seprated by commas. A scanned
|
||||
barcode will be attributed to a type if it matches one of its patterns.
|
||||
The patterns take the form of EAN13 barcodes. Numbers in the pattern must match
|
||||
the number in the scanned barcode. A 'x' or a '*' in a pattern will match
|
||||
any one number. If the patterns are shorter than EAN13 barcodes, they are assumed
|
||||
to be prefixes and match at the beginning. Weight, Price and Discount patterns also
|
||||
tell how the weight, price or discount is encoded in the barcode. 'N' indicate the
|
||||
positions where the integer part is en encoded, and 'D' where the decimals are encoded.
|
||||
If multiple pattern match one barcode, the longest pattern with the less 'x' or '*' is
|
||||
considered the matching one. If a barcode matches no pattern it will not be found in
|
||||
the POS.
|
||||
</p>
|
||||
<group col="4">
|
||||
|
||||
<field name="barcode_product" />
|
||||
<field name="barcode_cashier" />
|
||||
<field name="barcode_customer" />
|
||||
<field name="barcode_weight" />
|
||||
<field name="barcode_discount" />
|
||||
<field name="barcode_price" />
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
|
||||
</form>
|
||||
</field>
|
||||
|
||||
@@ -20,6 +20,19 @@
|
||||
.ui-dialog .ui-icon-closethick{
|
||||
float: right;
|
||||
}
|
||||
div.modal.in {
|
||||
position: absolute;
|
||||
background: white;
|
||||
padding: 20px;
|
||||
box-shadow: 0px 10px 20px black;
|
||||
border-radius: 3px;
|
||||
max-width: 600px;
|
||||
max-height: 400px;
|
||||
margin-top: -200px;
|
||||
margin-left: -300px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
}
|
||||
/* --- Generic Restyling and Resets --- */
|
||||
|
||||
html {
|
||||
@@ -483,10 +496,11 @@ td {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.pos .control-button.highlight{
|
||||
background: #6EC89B;
|
||||
border: solid 1px #6EC89B;
|
||||
color: white;
|
||||
.pos .control-button.highlight,
|
||||
.pos .button.highlight {
|
||||
background: #6EC89B !important;
|
||||
border: solid 1px #6EC89B !important;
|
||||
color: white !important;
|
||||
}
|
||||
.pos .control-button:active {
|
||||
background: #7F82AC;
|
||||
@@ -502,20 +516,20 @@ td {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* ********* The paypad contains the payment buttons ********* */
|
||||
/* ********* The actionpad (payment, set customer) ********* */
|
||||
|
||||
.pos .paypad {
|
||||
padding: 8px 4px 8px 8px;
|
||||
.pos .actionpad{
|
||||
padding: 8px 3px 8px 19px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
width: 205px;
|
||||
width: 183px;
|
||||
max-height: 350px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.pos .paypad button {
|
||||
height: 39px;
|
||||
.pos .actionpad .button {
|
||||
height: 50px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 0px -6px 4px -2px;
|
||||
@@ -524,13 +538,20 @@ td {
|
||||
color: #555555;
|
||||
font-size: 14px;
|
||||
}
|
||||
.pos .paypad button:active,
|
||||
.pos .numpad button:active,
|
||||
.pos .numpad .selected-mode,
|
||||
.pos .popup button:active{
|
||||
border: none;
|
||||
.pos .actionpad .button.pay {
|
||||
height: 158px;
|
||||
}
|
||||
.pos .actionpad .button.pay .fa {
|
||||
display: block;
|
||||
font-size: 32px;
|
||||
line-height: 54px;
|
||||
padding-top: 6px;
|
||||
background: rgb(86, 86, 86);
|
||||
color: white;
|
||||
background: #7f82ac;
|
||||
width: 60px;
|
||||
margin: auto;
|
||||
border-radius: 30px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* ********* The Numpad ********* */
|
||||
@@ -543,7 +564,7 @@ td {
|
||||
.pos .numpad button {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
margin: 0px 0px 4px 0px;
|
||||
margin: 0px 3px 4px 0px;
|
||||
font-weight: bold;
|
||||
vertical-align: middle;
|
||||
color: #555555;
|
||||
@@ -913,6 +934,9 @@ td {
|
||||
overflow-y: auto;
|
||||
border-right: dashed 1px rgb(215,215,215);
|
||||
}
|
||||
.screen .left-content.pc40{
|
||||
right: 66%;
|
||||
}
|
||||
.screen .right-content{
|
||||
position: absolute;
|
||||
right:0px; top: 64px; bottom: 0px;
|
||||
@@ -920,6 +944,9 @@ td {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.screen .right-content.pc60{
|
||||
left:34%
|
||||
}
|
||||
.screen .centered-content{
|
||||
position: absolute;
|
||||
right:25%; top: 64px; bottom: 0px;
|
||||
@@ -966,101 +993,125 @@ td {
|
||||
position:relative;
|
||||
}
|
||||
|
||||
|
||||
/* b) The payment screen */
|
||||
|
||||
|
||||
.pos .pos-payment-container {
|
||||
.pos .payment-numpad {
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
width: 360px;
|
||||
}
|
||||
.pos .payment-due-total {
|
||||
width: 50%;
|
||||
box-sizing: border-box;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 48px;
|
||||
margin: 27px;
|
||||
text-shadow: 0px 2px rgb(202, 202, 202);
|
||||
float: left;
|
||||
}
|
||||
.pos .payment-numpad .numpad button {
|
||||
width: 66px;
|
||||
height: 66px;
|
||||
}
|
||||
|
||||
.pos .paymentlines-container {
|
||||
padding: 16px;
|
||||
padding-top: 0;
|
||||
border-bottom: dashed 1px gainsboro;
|
||||
min-height: 154px;
|
||||
}
|
||||
|
||||
.pos .paymentlines {
|
||||
width: 100%;
|
||||
}
|
||||
.pos .paymentlines .controls {
|
||||
width: 40px;
|
||||
}
|
||||
.pos .paymentlines .label > * {
|
||||
font-size: 16px;
|
||||
padding: 8px;
|
||||
}
|
||||
.pos .paymentlines tbody{
|
||||
background: white;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.pos .paymentline{
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
border-box: 3px;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
border-radius: 3px;
|
||||
font-size: 22px;
|
||||
}
|
||||
.pos .paymentline-name{
|
||||
margin-bottom: 8px;
|
||||
.pos .paymentline.selected {
|
||||
font-size: 22px;
|
||||
background: #6EC89B;
|
||||
color: white;
|
||||
}
|
||||
.pos .paymentline-input{
|
||||
font-size: 20px;
|
||||
font-family: Lato;
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 6px 8px;
|
||||
.pos .paymentline.selected .edit {
|
||||
background: white;
|
||||
color: #484848;
|
||||
text-align: right;
|
||||
color: #6EC89B;
|
||||
outline: 3px blue;
|
||||
box-shadow: 0px 0px 0px 3px #6EC89B;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0px 2px rgba(143, 143, 143, 0.3) inset;
|
||||
}
|
||||
|
||||
.pos .paymentline-input:focus{
|
||||
color: rgb(130, 124, 255);
|
||||
box-shadow: 0px 2px rgba(219, 219, 219, 0.3) inset;
|
||||
-webkit-animation: all 250ms linear;
|
||||
.pos .paymentline > *{
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.paymentline-delete {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 8px;
|
||||
.pos .paymentline .col-due,
|
||||
.pos .paymentline .col-tendered,
|
||||
.pos .paymentline .col-change {
|
||||
min-width: 90px;
|
||||
}
|
||||
.pos .paymentline .col-change.highlight {
|
||||
background: rgb(184, 152, 204);
|
||||
}
|
||||
.pos .paymentline .col-name {
|
||||
font-size: 16px;
|
||||
}
|
||||
.pos .paymentline .delete-button{
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
.pos .payment-buttons {
|
||||
display: inline-block;
|
||||
width: 50%;
|
||||
box-sizing: border-box;
|
||||
padding: 16px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.pos .pos-payment-container .left-block{
|
||||
display: inline-block;
|
||||
width:49%;
|
||||
margin:0;
|
||||
padding:0;
|
||||
text-align:left;
|
||||
}
|
||||
.pos .pos-payment-container .infoline{
|
||||
margin-top:5px;
|
||||
margin-bottom:5px;
|
||||
.payment-screen .payment-buttons .button {
|
||||
background: rgb(221, 221, 221);
|
||||
line-height: 48px;
|
||||
margin-bottom: 4px;
|
||||
border-radius: 3px;
|
||||
font-size: 16px;
|
||||
padding: 0px 8px;
|
||||
opacity: 0.5;
|
||||
border: solid 1px rgb(202, 202, 202);
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
.pos .pos-payment-container .infoline.bigger{
|
||||
opacity: 1;
|
||||
font-size: 20px;
|
||||
.payment-screen .paymentlines-empty .total {
|
||||
text-align: center;
|
||||
padding: 24px 0px 18px;
|
||||
font-size: 64px;
|
||||
color: #43996E;
|
||||
text-shadow: 0px 2px white, 0px 2px 2px rgba(0, 0, 0, 0.27);
|
||||
}
|
||||
.pos .pos-payment-container .right-block{
|
||||
display: inline-block;
|
||||
width:49%;
|
||||
margin:0;
|
||||
padding:0;
|
||||
text-align:right;
|
||||
}
|
||||
.pos .paymentline.selected{
|
||||
background: rgb(220,220,220);
|
||||
.payment-screen .paymentlines-empty .message {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* c) The receipt screen */
|
||||
|
||||
.pos .receipt-screen .centered-content .button {
|
||||
line-height: 40px;
|
||||
padding: 3px 13px;
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
background: rgb(230, 230, 230);
|
||||
margin: 16px;
|
||||
margin-bottom: 0px;
|
||||
border-radius: 3px;
|
||||
border: solid 1px rgb(209, 209, 209);
|
||||
cursor: pointer;
|
||||
}
|
||||
.pos .pos-receipt-container {
|
||||
font-size: 0.75em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pos .pos-sale-ticket {
|
||||
@@ -1095,10 +1146,16 @@ td {
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
.oe_leftbar,
|
||||
.pos .pos-topheader,
|
||||
.pos .pos-leftpane,
|
||||
.pos .keyboard_frame {
|
||||
.pos .keyboard_frame,
|
||||
.pos .receipt-screen header,
|
||||
.pos .receipt-screen .top-content,
|
||||
.pos .receipt-screen .centered-content .button {
|
||||
display: none !important;
|
||||
}
|
||||
.pos,
|
||||
@@ -1119,12 +1176,17 @@ td {
|
||||
left: 0px !important;
|
||||
background-color: white;
|
||||
}
|
||||
.pos .receipt-screen header {
|
||||
display: none !important;
|
||||
}
|
||||
.pos .receipt-screen {
|
||||
text-align: left;
|
||||
}
|
||||
.pos .receipt-screen .centered-content{
|
||||
position: static;
|
||||
border: none;
|
||||
margin: none;
|
||||
}
|
||||
.pos .pos-receipt-container {
|
||||
text-align: left;
|
||||
}
|
||||
.pos-actionbar {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -1469,10 +1531,12 @@ td {
|
||||
font-size: 24px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.splitbill-screen .paymentmethods {
|
||||
.splitbill-screen .paymentmethods,
|
||||
.payment-screen .paymentmethods {
|
||||
margin: 16px;
|
||||
}
|
||||
.splitbill-screen .paymentmethod {
|
||||
.splitbill-screen .paymentmethod,
|
||||
.payment-screen .paymentmethod {
|
||||
background: rgb(221, 221, 221);
|
||||
line-height: 40px;
|
||||
margin-bottom: 4px;
|
||||
@@ -1482,6 +1546,14 @@ td {
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
.splitbill-screen .paymentmethod.active,
|
||||
.payment-screen .paymentmethod.active {
|
||||
background: #6EC89B;
|
||||
color: white;
|
||||
border-color: #6EC89B;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ********* The ActionBarWidget ********* */
|
||||
|
||||
@@ -1663,7 +1735,17 @@ td {
|
||||
.pos .popup .comment{
|
||||
font-weight: normal;
|
||||
font-size: 18px;
|
||||
margin: 0px 16px;
|
||||
margin: 0px 16px 16px 16px;
|
||||
}
|
||||
.pos .popup .comment.traceback {
|
||||
height: 264px;
|
||||
overflow: auto;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
font-family: 'Inconsolata';
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
.pos .popup .footer{
|
||||
position:absolute;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 522 B |
@@ -345,6 +345,9 @@ function openerp_pos_db(instance, module){
|
||||
});
|
||||
this.save('orders',orders);
|
||||
},
|
||||
remove_all_orders: function(){
|
||||
this.save('orders',[]);
|
||||
},
|
||||
get_orders: function(){
|
||||
return this.load('orders',[]);
|
||||
},
|
||||
|
||||
@@ -19,8 +19,6 @@ openerp.point_of_sale = function(instance) {
|
||||
|
||||
openerp_pos_widgets(instance,module); // import pos_widgets.js
|
||||
|
||||
openerp_pos_tests(instance,module); // import pos_tests.js
|
||||
|
||||
instance.web.client_actions.add('pos.ui', 'instance.point_of_sale.PosWidget');
|
||||
};
|
||||
|
||||
|
||||
@@ -114,180 +114,250 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
||||
return done;
|
||||
},
|
||||
|
||||
// helper function to load data from the server
|
||||
// helper function to load data from the server. Obsolete use the models loader below.
|
||||
fetch: function(model, fields, domain, ctx){
|
||||
this._load_progress = (this._load_progress || 0) + 0.05;
|
||||
this.pos_widget.loading_message(_t('Loading')+' '+model,this._load_progress);
|
||||
return new instance.web.Model(model).query(fields).filter(domain).context(ctx).all()
|
||||
},
|
||||
|
||||
// Server side model loaders. This is the list of the models that need to be loaded from
|
||||
// the server. The models are loaded one by one by this list's order. The 'loaded' callback
|
||||
// is used to store the data in the appropriate place once it has been loaded. This callback
|
||||
// can return a deferred that will pause the loading of the next module.
|
||||
// a shared temporary dictionary is available for loaders to communicate private variables
|
||||
// used during loading such as object ids, etc.
|
||||
models: [
|
||||
{
|
||||
model: 'res.users',
|
||||
fields: ['name','company_id'],
|
||||
domain: function(self){ return [['id','=',self.session.uid]]; },
|
||||
loaded: function(self,users){ self.user = users[0]; },
|
||||
},{
|
||||
model: 'res.company',
|
||||
fields: [ 'currency_id', 'email', 'website', 'company_registry', 'vat', 'name', 'phone', 'partner_id' ],
|
||||
domain: function(self){ return [['id','=',self.user.company_id[0]]]; },
|
||||
loaded: function(self,companies){ self.company = companies[0]; },
|
||||
},{
|
||||
model: 'product.uom',
|
||||
fields: [],
|
||||
domain: null,
|
||||
loaded: function(self,units){
|
||||
self.units = units;
|
||||
var units_by_id = {};
|
||||
for(var i = 0, len = units.length; i < len; i++){
|
||||
units_by_id[units[i].id] = units[i];
|
||||
units[i].groupable = ( units[i].category_id[0] === 1 );
|
||||
units[i].is_unit = ( units[i].id === 1 );
|
||||
}
|
||||
self.units_by_id = units_by_id;
|
||||
}
|
||||
},{
|
||||
model: 'res.users',
|
||||
fields: ['name','ean13'],
|
||||
domain: null,
|
||||
loaded: function(self,users){ self.users = users; },
|
||||
},{
|
||||
model: 'res.partner',
|
||||
fields: ['name','street','city','country_id','phone','zip','mobile','email','ean13'],
|
||||
domain: null,
|
||||
loaded: function(self,partners){
|
||||
self.partners = partners;
|
||||
self.db.add_partners(partners);
|
||||
},
|
||||
},{
|
||||
model: 'account.tax',
|
||||
fields: ['name','amount', 'price_include', 'type'],
|
||||
domain: null,
|
||||
loaded: function(self,taxes){ self.taxes = taxes; },
|
||||
},{
|
||||
model: 'pos.session',
|
||||
fields: ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at','sequence_number'],
|
||||
domain: function(self){ return [['state','=','opened'],['user_id','=',self.session.uid]]; },
|
||||
loaded: function(self,pos_sessions){ self.pos_session = pos_sessions[0]; },
|
||||
},{
|
||||
model: 'pos.config',
|
||||
fields: [],
|
||||
domain: function(self){ return [['id','=', self.pos_session.config_id[0]]]; },
|
||||
loaded: function(self,configs){
|
||||
self.config = configs[0];
|
||||
self.config.use_proxy = self.config.iface_payment_terminal ||
|
||||
self.config.iface_electronic_scale ||
|
||||
self.config.iface_print_via_proxy ||
|
||||
self.config.iface_scan_via_proxy ||
|
||||
self.config.iface_cashdrawer;
|
||||
|
||||
self.barcode_reader.add_barcode_patterns({
|
||||
'product': self.config.barcode_product,
|
||||
'cashier': self.config.barcode_cashier,
|
||||
'client': self.config.barcode_customer,
|
||||
'weight': self.config.barcode_weight,
|
||||
'discount': self.config.barcode_discount,
|
||||
'price': self.config.barcode_price,
|
||||
});
|
||||
},
|
||||
},{
|
||||
model: 'stock.location',
|
||||
fields: [],
|
||||
domain: function(self){ return [['id','=', self.config.stock_location_id[0]]]; },
|
||||
loaded: function(self, locations){ self.shop = locations[0]; },
|
||||
},{
|
||||
model: 'product.pricelist',
|
||||
fields: ['currency_id'],
|
||||
domain: function(self){ return [['id','=',self.config.pricelist_id[0]]]; },
|
||||
loaded: function(self, pricelists){ self.pricelist = pricelists[0]; },
|
||||
},{
|
||||
model: 'res.currency',
|
||||
fields: ['symbol','position','rounding','accuracy'],
|
||||
domain: function(self){ return [['id','=',self.pricelist.currency_id[0]]]; },
|
||||
loaded: function(self, currencies){
|
||||
self.currency = currencies[0];
|
||||
},
|
||||
},{
|
||||
model: 'product.packaging',
|
||||
fields: ['ean','product_tmpl_id'],
|
||||
domain: null,
|
||||
loaded: function(self, packagings){
|
||||
self.db.add_packagings(packagings);
|
||||
},
|
||||
},{
|
||||
model: 'pos.category',
|
||||
fields: ['id','name','parent_id','child_id','image'],
|
||||
domain: null,
|
||||
loaded: function(self, categories){
|
||||
self.db.add_categories(categories);
|
||||
},
|
||||
},{
|
||||
model: 'product.product',
|
||||
fields: ['name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', 'default_code', 'variants',
|
||||
'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', 'description_sale', 'description',
|
||||
'product_tmpl_id'],
|
||||
domain: function(self){ return [['sale_ok','=',true],['available_in_pos','=',true]]; },
|
||||
context: function(self){ return { pricelist: self.pricelist.id }; },
|
||||
loaded: function(self, products){
|
||||
self.db.add_products(products);
|
||||
},
|
||||
},{
|
||||
model: 'account.bank.statement',
|
||||
fields: ['account_id','currency','journal_id','state','name','user_id','pos_session_id'],
|
||||
domain: function(self){ return [['state', '=', 'open'],['pos_session_id', '=', self.pos_session.id]]; },
|
||||
loaded: function(self, bankstatements, tmp){
|
||||
self.bankstatements = bankstatements;
|
||||
|
||||
tmp.journals = [];
|
||||
_.each(bankstatements,function(statement){
|
||||
tmp.journals.push(statement.journal_id[0]);
|
||||
});
|
||||
},
|
||||
},{
|
||||
model: 'account.journal',
|
||||
fields: [],
|
||||
domain: function(self,tmp){ return [['id','in',tmp.journals]]; },
|
||||
loaded: function(self, journals){
|
||||
self.journals = journals;
|
||||
|
||||
// associate the bank statements with their journals.
|
||||
var bankstatements = self.bankstatements;
|
||||
for(var i = 0, ilen = bankstatements.length; i < ilen; i++){
|
||||
for(var j = 0, jlen = journals.length; j < jlen; j++){
|
||||
if(bankstatements[i].journal_id[0] === journals[j].id){
|
||||
bankstatements[i].journal = journals[j];
|
||||
bankstatements[i].self_checkout_payment_method = journals[j].self_checkout_payment_method;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.cashregisters = bankstatements;
|
||||
},
|
||||
},{
|
||||
loaded: function(self){
|
||||
self.company_logo = new Image();
|
||||
self.company_logo.crossOrigin = 'anonymous';
|
||||
var logo_loaded = new $.Deferred();
|
||||
self.company_logo.onload = function(){
|
||||
var img = self.company_logo;
|
||||
var ratio = 1;
|
||||
var targetwidth = 300;
|
||||
var maxheight = 150;
|
||||
if( img.width !== targetwidth ){
|
||||
ratio = targetwidth / img.width;
|
||||
}
|
||||
if( img.height * ratio > maxheight ){
|
||||
ratio = maxheight / img.height;
|
||||
}
|
||||
var width = Math.floor(img.width * ratio);
|
||||
var height = Math.floor(img.height * ratio);
|
||||
var c = document.createElement('canvas');
|
||||
c.width = width;
|
||||
c.height = height
|
||||
var ctx = c.getContext('2d');
|
||||
ctx.drawImage(self.company_logo,0,0, width, height);
|
||||
|
||||
self.company_logo_base64 = c.toDataURL();
|
||||
window.logo64 = self.company_logo_base64;
|
||||
logo_loaded.resolve();
|
||||
};
|
||||
self.company_logo.onerror = function(){
|
||||
logo_loaded.reject();
|
||||
};
|
||||
self.company_logo.src = window.location.origin + '/web/binary/company_logo';
|
||||
|
||||
return logo_loaded;
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
// loads all the needed data on the sever. returns a deferred indicating when all the data has loaded.
|
||||
load_server_data: function(){
|
||||
var self = this;
|
||||
var loaded = new $.Deferred();
|
||||
var progress = 0;
|
||||
var progress_step = 1.0 / self.models.length;
|
||||
var tmp = {}; // this is used to share a temporary state between models loaders
|
||||
|
||||
var loaded = self.fetch('res.users',['name','company_id'],[['id','=',this.session.uid]])
|
||||
.then(function(users){
|
||||
self.user = users[0];
|
||||
|
||||
return self.fetch('res.company',
|
||||
[
|
||||
'currency_id',
|
||||
'email',
|
||||
'website',
|
||||
'company_registry',
|
||||
'vat',
|
||||
'name',
|
||||
'phone',
|
||||
'partner_id',
|
||||
],
|
||||
[['id','=',users[0].company_id[0]]],
|
||||
{show_address_only: true});
|
||||
}).then(function(companies){
|
||||
self.company = companies[0];
|
||||
|
||||
return self.fetch('product.uom', null, null);
|
||||
}).then(function(units){
|
||||
self.units = units;
|
||||
var units_by_id = {};
|
||||
for(var i = 0, len = units.length; i < len; i++){
|
||||
units_by_id[units[i].id] = units[i];
|
||||
units[i].groupable = ( units[i].category_id[0] === 1 );
|
||||
units[i].is_unit = ( units[i].id === 1 );
|
||||
}
|
||||
self.units_by_id = units_by_id;
|
||||
function load_model(index){
|
||||
if(index >= self.models.length){
|
||||
loaded.resolve();
|
||||
}else{
|
||||
var model = self.models[index];
|
||||
self.pos_widget.loading_message(_t('Loading')+' '+(model.model || ''), progress);
|
||||
var fields = typeof model.fields === 'function' ? model.fields(self,tmp) : model.fields;
|
||||
var domain = typeof model.domain === 'function' ? model.domain(self,tmp) : model.domain;
|
||||
var context = typeof model.context === 'function' ? model.context(self,tmp) : model.context;
|
||||
progress += progress_step;
|
||||
|
||||
return self.fetch('res.users', ['name','ean13'], [['ean13', '!=', false]]);
|
||||
}).then(function(users){
|
||||
self.users = users;
|
||||
|
||||
return self.fetch('res.partner', ['name','street','city','country_id','phone','zip','mobile','email','ean13']);
|
||||
}).then(function(partners){
|
||||
self.partners = partners;
|
||||
self.db.add_partners(partners);
|
||||
|
||||
return self.fetch('account.tax', ['name','amount', 'price_include', 'type']);
|
||||
}).then(function(taxes){
|
||||
self.taxes = taxes;
|
||||
|
||||
return self.fetch(
|
||||
'pos.session',
|
||||
['id', 'journal_ids','name','user_id','config_id','start_at','stop_at','sequence_number'],
|
||||
[['state', '=', 'opened'], ['user_id', '=', self.session.uid]]
|
||||
);
|
||||
}).then(function(pos_sessions){
|
||||
self.pos_session = pos_sessions[0];
|
||||
|
||||
return self.fetch('pos.config',[],[['id','=', self.pos_session.config_id[0]]]);
|
||||
}).then(function(configs){
|
||||
self.config = configs[0];
|
||||
self.config.use_proxy = self.config.iface_payment_terminal ||
|
||||
self.config.iface_electronic_scale ||
|
||||
self.config.iface_print_via_proxy ||
|
||||
self.config.iface_scan_via_proxy ||
|
||||
self.config.iface_cashdrawer;
|
||||
|
||||
self.barcode_reader.add_barcode_patterns({
|
||||
'product': self.config.barcode_product,
|
||||
'cashier': self.config.barcode_cashier,
|
||||
'client': self.config.barcode_customer,
|
||||
'weight': self.config.barcode_weight,
|
||||
'discount': self.config.barcode_discount,
|
||||
'price': self.config.barcode_price,
|
||||
});
|
||||
return self.fetch('stock.location',[],[['id','=', self.config.stock_location_id[0]]]);
|
||||
}).then(function(shops){
|
||||
self.shop = shops[0];
|
||||
|
||||
return self.fetch('product.pricelist',['currency_id'],[['id','=',self.config.pricelist_id[0]]]);
|
||||
}).then(function(pricelists){
|
||||
self.pricelist = pricelists[0];
|
||||
|
||||
return self.fetch('res.currency',['symbol','position','rounding','accuracy'],[['id','=',self.pricelist.currency_id[0]]]);
|
||||
}).then(function(currencies){
|
||||
self.currency = currencies[0];
|
||||
|
||||
return self.fetch('product.packaging',['ean','product_tmpl_id']);
|
||||
}).then(function(packagings){
|
||||
self.db.add_packagings(packagings);
|
||||
|
||||
return self.fetch('pos.category', ['id','name','parent_id','child_id','image']);
|
||||
}).then(function(categories){
|
||||
self.db.add_categories(categories);
|
||||
|
||||
return self.fetch(
|
||||
'product.product',
|
||||
['name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', 'default_code', 'variants',
|
||||
'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', 'description_sale', 'description',
|
||||
'product_tmpl_id'],
|
||||
[['sale_ok','=',true],['available_in_pos','=',true]],
|
||||
{pricelist: self.pricelist.id} // context for price
|
||||
);
|
||||
}).then(function(products){
|
||||
self.db.add_products(products);
|
||||
|
||||
return self.fetch(
|
||||
'account.bank.statement',
|
||||
['account_id','currency','journal_id','state','name','user_id','pos_session_id'],
|
||||
[['state','=','open'],['pos_session_id', '=', self.pos_session.id]]
|
||||
);
|
||||
}).then(function(bankstatements){
|
||||
var journals = [];
|
||||
_.each(bankstatements,function(statement) {
|
||||
journals.push(statement.journal_id[0]);
|
||||
});
|
||||
self.bankstatements = bankstatements;
|
||||
return self.fetch('account.journal', undefined, [['id','in', journals]]);
|
||||
}).then(function(journals){
|
||||
self.journals = journals;
|
||||
|
||||
// associate the bank statements with their journals.
|
||||
var bankstatements = self.bankstatements;
|
||||
for(var i = 0, ilen = bankstatements.length; i < ilen; i++){
|
||||
for(var j = 0, jlen = journals.length; j < jlen; j++){
|
||||
if(bankstatements[i].journal_id[0] === journals[j].id){
|
||||
bankstatements[i].journal = journals[j];
|
||||
bankstatements[i].self_checkout_payment_method = journals[j].self_checkout_payment_method;
|
||||
}
|
||||
if( model.model ){
|
||||
new instance.web.Model(model.model).query(fields).filter(domain).context(context).all()
|
||||
.then(function(result){
|
||||
try{ // catching exceptions in model.loaded(...)
|
||||
$.when(model.loaded(self,result,tmp))
|
||||
.then(function(){ load_model(index + 1); },
|
||||
function(err){ loaded.reject(err); });
|
||||
}catch(err){
|
||||
loaded.reject(err);
|
||||
}
|
||||
},function(err){
|
||||
loaded.reject(err);
|
||||
});
|
||||
}else if( model.loaded ){
|
||||
try{ // catching exceptions in model.loaded(...)
|
||||
$.when(model.loaded(self,tmp))
|
||||
.then( function(){ load_model(index +1); },
|
||||
function(err){ loaded.reject(err); });
|
||||
}catch(err){
|
||||
loaded.reject(err);
|
||||
}
|
||||
}else{
|
||||
load_model(index + 1);
|
||||
}
|
||||
self.cashregisters = bankstatements;
|
||||
}
|
||||
}
|
||||
|
||||
// Load the company Logo
|
||||
try{
|
||||
load_model(0);
|
||||
}catch(err){
|
||||
loaded.reject(err);
|
||||
}
|
||||
|
||||
self.company_logo = new Image();
|
||||
self.company_logo.crossOrigin = 'anonymous';
|
||||
var logo_loaded = new $.Deferred();
|
||||
self.company_logo.onload = function(){
|
||||
var img = self.company_logo;
|
||||
var ratio = 1;
|
||||
var targetwidth = 300;
|
||||
var maxheight = 150;
|
||||
if( img.width !== targetwidth ){
|
||||
ratio = targetwidth / img.width;
|
||||
}
|
||||
if( img.height * ratio > maxheight ){
|
||||
ratio = maxheight / img.height;
|
||||
}
|
||||
var width = Math.floor(img.width * ratio);
|
||||
var height = Math.floor(img.height * ratio);
|
||||
var c = document.createElement('canvas');
|
||||
c.width = width;
|
||||
c.height = height
|
||||
var ctx = c.getContext('2d');
|
||||
ctx.drawImage(self.company_logo,0,0, width, height);
|
||||
|
||||
self.company_logo_base64 = c.toDataURL();
|
||||
window.logo64 = self.company_logo_base64;
|
||||
logo_loaded.resolve();
|
||||
};
|
||||
self.company_logo.onerror = function(){
|
||||
logo_loaded.reject();
|
||||
};
|
||||
self.company_logo.src = window.location.origin + '/web/binary/company_logo';
|
||||
|
||||
return logo_loaded;
|
||||
});
|
||||
|
||||
return loaded;
|
||||
},
|
||||
|
||||
@@ -324,20 +394,20 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
||||
// it returns a deferred that succeeds after having tried to send the order and all the other pending orders.
|
||||
push_order: function(order) {
|
||||
var self = this;
|
||||
this.proxy.log('push_order',order.export_as_JSON());
|
||||
var order_id = this.db.add_order(order.export_as_JSON());
|
||||
|
||||
if(order){
|
||||
this.proxy.log('push_order',order.export_as_JSON());
|
||||
this.db.add_order(order.export_as_JSON());
|
||||
}
|
||||
|
||||
var pushed = new $.Deferred();
|
||||
|
||||
this.set('synch',{state:'connecting', pending:self.db.get_orders().length});
|
||||
|
||||
this.flush_mutex.exec(function(){
|
||||
var flushed = self._flush_all_orders();
|
||||
var flushed = self._flush_orders(self.db.get_orders());
|
||||
|
||||
flushed.always(function(){
|
||||
flushed.always(function(ids){
|
||||
pushed.resolve();
|
||||
});
|
||||
|
||||
return flushed;
|
||||
});
|
||||
return pushed;
|
||||
},
|
||||
@@ -361,8 +431,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
||||
|
||||
var order_id = this.db.add_order(order.export_as_JSON());
|
||||
|
||||
this.set('synch',{state:'connecting', pending:self.db.get_orders().length});
|
||||
|
||||
this.flush_mutex.exec(function(){
|
||||
var done = new $.Deferred(); // holds the mutex
|
||||
|
||||
@@ -373,7 +441,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
||||
// things will happen as a duplicate will be sent next time
|
||||
// so we must make sure the server detects and ignores duplicated orders
|
||||
|
||||
var transfer = self._flush_order(order_id, {timeout:30000, to_invoice:true});
|
||||
var transfer = self._flush_orders([self.db.get_order(order_id)], {timeout:30000, to_invoice:true});
|
||||
|
||||
transfer.fail(function(){
|
||||
invoiced.reject('error-transfer');
|
||||
@@ -382,10 +450,12 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
||||
|
||||
// on success, get the order id generated by the server
|
||||
transfer.pipe(function(order_server_id){
|
||||
|
||||
// generate the pdf and download it
|
||||
self.pos_widget.do_action('point_of_sale.pos_invoice_report',{additional_context:{
|
||||
active_ids:order_server_id,
|
||||
}});
|
||||
|
||||
invoiced.resolve();
|
||||
done.resolve();
|
||||
});
|
||||
@@ -397,62 +467,33 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
||||
return invoiced;
|
||||
},
|
||||
|
||||
// attemps to send all pending orders ( stored in the pos_db ) to the server,
|
||||
// and remove the successfully sent ones from the db once
|
||||
// it has been confirmed that they have been sent correctly.
|
||||
flush: function() {
|
||||
// wrapper around the _save_to_server that updates the synch status widget
|
||||
_flush_orders: function(orders, options) {
|
||||
var self = this;
|
||||
var flushed = new $.Deferred();
|
||||
|
||||
this.flush_mutex.exec(function(){
|
||||
var done = new $.Deferred();
|
||||
this.set('synch',{ state: 'connecting', pending: orders.length});
|
||||
|
||||
self._flush_all_orders()
|
||||
.done( function(){ flushed.resolve();})
|
||||
.fail( function(){ flushed.reject(); })
|
||||
.always(function(){ done.resolve(); });
|
||||
|
||||
return done;
|
||||
});
|
||||
|
||||
return flushed;
|
||||
},
|
||||
|
||||
// attempts to send the locally stored order of id 'order_id'
|
||||
// the sending is asynchronous and can take some time to decide if it is successful or not
|
||||
// it is therefore important to only call this method from inside a mutex
|
||||
// this method returns a deferred indicating wether the sending was successful or not
|
||||
// there is a timeout parameter which is set to 2 seconds by default.
|
||||
_flush_order: function( order_id, options) {
|
||||
return this._flush_all_orders([this.db.get_order(order_id)], options);
|
||||
},
|
||||
|
||||
// attempts to send all the locally stored orders. As with _flush_order, it should only be
|
||||
// called from within a mutex.
|
||||
// this method returns a deferred that always succeeds when all orders have been tried to be sent,
|
||||
// even if none of them could actually be sent.
|
||||
_flush_all_orders: function () {
|
||||
var self = this;
|
||||
self.set('synch', {
|
||||
state: 'connecting',
|
||||
pending: self.get('synch').pending
|
||||
});
|
||||
return self._save_to_server(self.db.get_orders()).done(function () {
|
||||
return self._save_to_server(orders, options).done(function (server_ids) {
|
||||
var pending = self.db.get_orders().length;
|
||||
|
||||
self.set('synch', {
|
||||
state: pending ? 'connecting' : 'connected',
|
||||
pending: pending
|
||||
});
|
||||
|
||||
return server_ids;
|
||||
});
|
||||
},
|
||||
|
||||
// send an array of orders to the server
|
||||
// available options:
|
||||
// - timeout: timeout for the rpc call in ms
|
||||
// returns a deferred that resolves with the list of
|
||||
// server generated ids for the sent orders
|
||||
_save_to_server: function (orders, options) {
|
||||
if (!orders || !orders.length) {
|
||||
var result = $.Deferred();
|
||||
result.resolve();
|
||||
result.resolve([]);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -474,11 +515,18 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
||||
shadow: !options.to_invoice,
|
||||
timeout: timeout
|
||||
}
|
||||
).then(function () {
|
||||
).then(function (server_ids) {
|
||||
_.each(orders, function (order) {
|
||||
self.db.remove_order(order.id);
|
||||
});
|
||||
}).fail(function (unused, event){
|
||||
return server_ids;
|
||||
}).fail(function (error, event){
|
||||
if(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
|
||||
});
|
||||
}
|
||||
// prevent an error popup creation by the rpc failure
|
||||
// we want the failure to be silent as we send the orders in the background
|
||||
event.preventDefault();
|
||||
@@ -816,6 +864,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
||||
this.receipt_type = 'receipt'; // 'receipt' || 'invoice'
|
||||
this.temporary = attributes.temporary || false;
|
||||
this.sequence_number = this.pos.pos_session.sequence_number++;
|
||||
this.to_invoice = false;
|
||||
return this;
|
||||
},
|
||||
is_empty: function(){
|
||||
@@ -948,11 +997,47 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
||||
return sum + paymentLine.get_amount();
|
||||
}), 0);
|
||||
},
|
||||
getChange: function() {
|
||||
return this.getPaidTotal() - this.getTotalTaxIncluded();
|
||||
getChange: function(paymentline) {
|
||||
if (!paymentline) {
|
||||
var change = this.getPaidTotal() - this.getTotalTaxIncluded();
|
||||
} else {
|
||||
var change = -this.getTotalTaxIncluded();
|
||||
var lines = this.get('paymentLines').models;
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
change += lines[i].get_amount();
|
||||
if (lines[i] === paymentline) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return round_pr(Math.max(0,change), this.pos.currency.rounding);
|
||||
},
|
||||
getDueLeft: function() {
|
||||
return this.getTotalTaxIncluded() - this.getPaidTotal();
|
||||
getDueLeft: function(paymentline) {
|
||||
if (!paymentline) {
|
||||
var due = this.getTotalTaxIncluded() - this.getPaidTotal();
|
||||
} else {
|
||||
var due = this.getTotalTaxIncluded();
|
||||
var lines = this.get('paymentLines').models;
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
if (lines[i] === paymentline) {
|
||||
break;
|
||||
} else {
|
||||
due -= lines[i].get_amount();
|
||||
}
|
||||
}
|
||||
}
|
||||
return round_pr(Math.max(0,due), this.pos.currency.rounding);
|
||||
},
|
||||
isPaid: function(){
|
||||
return this.getDueLeft() === 0;
|
||||
},
|
||||
isPaidWithCash: function(){
|
||||
return !!this.get('paymentLines').find( function(pl){
|
||||
return pl.cashregister.journal.type === 'cash';
|
||||
});
|
||||
},
|
||||
finalize: function(){
|
||||
this.destroy();
|
||||
},
|
||||
// sets the type of receipt 'receipt'(default) or 'invoice'
|
||||
set_receipt_type: function(type){
|
||||
@@ -984,6 +1069,25 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
||||
}
|
||||
}
|
||||
},
|
||||
set_to_invoice: function(to_invoice) {
|
||||
this.to_invoice = to_invoice;
|
||||
},
|
||||
is_to_invoice: function(){
|
||||
return this.to_invoice;
|
||||
},
|
||||
// remove all the paymentlines with zero money in it
|
||||
clean_empty_paymentlines: function() {
|
||||
var lines = this.get('paymentLines').models;
|
||||
var empty = [];
|
||||
for ( var i = 0; i < lines.length; i++) {
|
||||
if (!lines[i].get_amount()) {
|
||||
empty.push(lines[i]);
|
||||
}
|
||||
}
|
||||
for ( var i = 0; i < empty.length; i++) {
|
||||
this.removePaymentline(empty[i]);
|
||||
}
|
||||
},
|
||||
//see set_screen_data
|
||||
get_screen_data: function(key){
|
||||
return this.screen_data[key];
|
||||
|
||||
@@ -73,8 +73,9 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
||||
load_saved_screen: function(){
|
||||
this.close_popup();
|
||||
var selectedOrder = this.pos.get('selectedOrder');
|
||||
// this.set_current_screen(selectedOrder.get_screen_data('screen') || this.default_screen,null,'refresh');
|
||||
this.set_current_screen(this.default_screen,null,'refresh');
|
||||
// FIXME : this changing screen behaviour is sometimes confusing ...
|
||||
this.set_current_screen(selectedOrder.get_screen_data('screen') || this.default_screen,null,'refresh');
|
||||
//this.set_current_screen(this.default_screen,null,'refresh');
|
||||
|
||||
},
|
||||
set_user_mode: function(user_mode){
|
||||
@@ -210,26 +211,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
||||
barcode_error_action: function(code){
|
||||
this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
|
||||
},
|
||||
// shows an action bar on the screen. The actionbar is automatically shown when you add a button
|
||||
// with add_action_button()
|
||||
show_action_bar: function(){
|
||||
this.pos_widget.action_bar.show();
|
||||
},
|
||||
|
||||
// hides the action bar. The actionbar is automatically hidden when it is empty
|
||||
hide_action_bar: function(){
|
||||
this.pos_widget.action_bar.hide();
|
||||
},
|
||||
|
||||
// adds a new button to the action bar. The button definition takes three parameters, all optional :
|
||||
// - label: the text below the button
|
||||
// - icon: a small icon that will be shown
|
||||
// - click: a callback that will be executed when the button is clicked.
|
||||
// the method returns a reference to the button widget, and automatically show the actionbar.
|
||||
add_action_button: function(button_def){
|
||||
this.show_action_bar();
|
||||
return this.pos_widget.action_bar.add_new_button(button_def);
|
||||
},
|
||||
|
||||
// this method shows the screen and sets up all the widget related to this screen. Extend this method
|
||||
// if you want to alter the behavior of the screen.
|
||||
@@ -241,12 +222,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
||||
this.$el.removeClass('oe_hidden');
|
||||
}
|
||||
|
||||
if(this.pos_widget.action_bar.get_button_count() > 0){
|
||||
this.show_action_bar();
|
||||
}else{
|
||||
this.hide_action_bar();
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
this.pos_widget.set_numpad_visible(this.show_numpad);
|
||||
@@ -269,7 +244,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
||||
if(this.pos.barcode_reader){
|
||||
this.pos.barcode_reader.reset_action_callbacks();
|
||||
}
|
||||
this.pos_widget.action_bar.destroy_buttons();
|
||||
},
|
||||
|
||||
// this methods hides the screen. It's not a good place to put your cleanup stuff as it is called on the
|
||||
@@ -314,6 +288,26 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
||||
});
|
||||
|
||||
|
||||
module.FullscreenPopup = module.PopUpWidget.extend({
|
||||
template:'FullscreenPopupWidget',
|
||||
show: function(){
|
||||
var self = this;
|
||||
this._super();
|
||||
this.renderElement();
|
||||
this.$('.button.fullscreen').off('click').click(function(){
|
||||
window.document.body.webkitRequestFullscreen();
|
||||
self.pos_widget.screen_selector.close_popup();
|
||||
});
|
||||
this.$('.button.cancel').off('click').click(function(){
|
||||
self.pos_widget.screen_selector.close_popup();
|
||||
});
|
||||
},
|
||||
ismobile: function(){
|
||||
return typeof window.orientation !== 'undefined';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.ChooseReceiptPopupWidget = module.PopUpWidget.extend({
|
||||
template:'ChooseReceiptPopupWidget',
|
||||
show: function(){
|
||||
@@ -348,6 +342,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
||||
var self = this;
|
||||
this._super();
|
||||
|
||||
$('body').append('<audio src="/point_of_sale/static/src/sounds/error.wav" autoplay="true"></audio>');
|
||||
|
||||
if( text && (text.message || text.comment) ){
|
||||
this.$('.message').text(text.message);
|
||||
this.$('.comment').text(text.comment);
|
||||
@@ -365,6 +361,9 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
||||
},
|
||||
});
|
||||
|
||||
module.ErrorTracebackPopupWidget = module.ErrorPopupWidget.extend({
|
||||
template:'ErrorTracebackPopupWidget',
|
||||
});
|
||||
|
||||
module.ErrorSessionPopupWidget = module.ErrorPopupWidget.extend({
|
||||
template:'ErrorSessionPopupWidget',
|
||||
@@ -375,6 +374,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
||||
show: function(barcode){
|
||||
this._super();
|
||||
this.$('.barcode').text(barcode);
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
@@ -404,14 +404,22 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
||||
},
|
||||
});
|
||||
|
||||
module.ErrorNoClientPopupWidget = module.ErrorPopupWidget.extend({
|
||||
template: 'ErrorNoClientPopupWidget',
|
||||
});
|
||||
|
||||
module.ErrorInvoiceTransferPopupWidget = module.ErrorPopupWidget.extend({
|
||||
template: 'ErrorInvoiceTransferPopupWidget',
|
||||
});
|
||||
|
||||
module.UnsentOrdersPopupWidget = module.PopUpWidget.extend({
|
||||
template: 'UnsentOrdersPopupWidget',
|
||||
show: function(options){
|
||||
var self = this;
|
||||
this._super(options);
|
||||
this.renderElement();
|
||||
this.$('.button.confirm').click(function(){
|
||||
self.pos_widget.screen_selector.close_popup();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
module.ScaleScreenWidget = module.ScreenWidget.extend({
|
||||
template:'ScaleScreenWidget',
|
||||
|
||||
@@ -557,6 +565,11 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
||||
module.ClientListScreenWidget = module.ScreenWidget.extend({
|
||||
template: 'ClientListScreenWidget',
|
||||
|
||||
init: function(parent, options){
|
||||
this._super(parent, options);
|
||||
this.partner_cache = new module.DomCache();
|
||||
},
|
||||
|
||||
show_leftpane: false,
|
||||
|
||||
auto_back: true,
|
||||
@@ -587,7 +600,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
||||
}
|
||||
|
||||
this.$('.client-list-contents').delegate('.client-line','click',function(event){
|
||||
console.log('uh');
|
||||
self.line_select(event,$(this),parseInt($(this).data('id')));
|
||||
});
|
||||
|
||||
@@ -628,14 +640,24 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
||||
this.$('.searchbox input').focus();
|
||||
},
|
||||
render_list: function(partners){
|
||||
var contents = this.$('.client-list-contents');
|
||||
contents.empty();
|
||||
var contents = this.$el[0].querySelector('.client-list-contents');
|
||||
contents.innerHtml = "";
|
||||
for(var i = 0, len = partners.length; i < len; i++){
|
||||
var clientline = $(QWeb.render('ClientLine',{partner:partners[i]}));
|
||||
if( partners[i] === this.new_client ){
|
||||
clientline.addClass('highlight');
|
||||
var partner = partners[i];
|
||||
var clientline = this.partner_cache.get_node(partner.id);
|
||||
if(!clientline){
|
||||
var clientline_html = QWeb.render('ClientLine',{partner:partners[i]});
|
||||
var clientline = document.createElement('tbody');
|
||||
clientline.innerHTML = clientline_html;
|
||||
clientline = clientline.childNodes[1];
|
||||
this.partner_cache.cache_node(partner.id,clientline);
|
||||
}
|
||||
contents.append(clientline);
|
||||
if( partners === this.new_client ){
|
||||
clientline.classList.add('highlight');
|
||||
}else{
|
||||
clientline.classList.remove('highlight');
|
||||
}
|
||||
contents.appendChild(clientline);
|
||||
}
|
||||
},
|
||||
save_changes: function(){
|
||||
@@ -730,30 +752,16 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
||||
|
||||
module.ReceiptScreenWidget = module.ScreenWidget.extend({
|
||||
template: 'ReceiptScreenWidget',
|
||||
|
||||
show_numpad: true,
|
||||
show_leftpane: true,
|
||||
show_numpad: false,
|
||||
show_leftpane: false,
|
||||
|
||||
show: function(){
|
||||
this._super();
|
||||
var self = this;
|
||||
|
||||
var print_button = this.add_action_button({
|
||||
label: _t('Print'),
|
||||
icon: '/point_of_sale/static/src/img/icons/png48/printer.png',
|
||||
click: function(){ self.print(); },
|
||||
});
|
||||
|
||||
var finish_button = this.add_action_button({
|
||||
label: _t('Next Order'),
|
||||
icon: '/point_of_sale/static/src/img/icons/png48/go-next.png',
|
||||
click: function() { self.finishOrder(); },
|
||||
});
|
||||
|
||||
this.refresh();
|
||||
this.print();
|
||||
|
||||
//
|
||||
// The problem is that in chrome the print() is asynchronous and doesn't
|
||||
// execute until all rpc are finished. So it conflicts with the rpc used
|
||||
// to send the orders to the backend, and the user is able to go to the next
|
||||
@@ -770,372 +778,345 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
||||
// 2 seconds is the same as the default timeout for sending orders and so the dialog
|
||||
// should have appeared before the timeout... so yeah that's not ultra reliable.
|
||||
|
||||
finish_button.set_disabled(true);
|
||||
this.lock_screen(true);
|
||||
setTimeout(function(){
|
||||
finish_button.set_disabled(false);
|
||||
self.lock_screen(false);
|
||||
}, 2000);
|
||||
},
|
||||
lock_screen: function(locked) {
|
||||
this._locked = locked;
|
||||
if (locked) {
|
||||
this.$('.next').removeClass('highlight');
|
||||
} else {
|
||||
this.$('.next').addClass('highlight');
|
||||
}
|
||||
},
|
||||
print: function() {
|
||||
window.print();
|
||||
},
|
||||
finishOrder: function() {
|
||||
this.pos.get('selectedOrder').destroy();
|
||||
finish_order: function() {
|
||||
if (!this._locked) {
|
||||
this.pos.get_order().finalize();
|
||||
}
|
||||
},
|
||||
renderElement: function() {
|
||||
var self = this;
|
||||
this._super();
|
||||
this.$('.next').click(function(){
|
||||
self.finish_order();
|
||||
});
|
||||
this.$('.button.print').click(function(){
|
||||
self.print();
|
||||
});
|
||||
},
|
||||
refresh: function() {
|
||||
var order = this.pos.get('selectedOrder');
|
||||
$('.pos-receipt-container', this.$el).html(QWeb.render('PosTicket',{
|
||||
var order = this.pos.get_order();
|
||||
this.$('.pos-receipt-container').html(QWeb.render('PosTicket',{
|
||||
widget:this,
|
||||
order: order,
|
||||
orderlines: order.get('orderLines').models,
|
||||
paymentlines: order.get('paymentLines').models,
|
||||
}));
|
||||
},
|
||||
close: function(){
|
||||
this._super();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.PaymentScreenWidget = module.ScreenWidget.extend({
|
||||
template: 'PaymentScreenWidget',
|
||||
back_screen: 'products',
|
||||
next_screen: 'receipt',
|
||||
template: 'PaymentScreenWidget',
|
||||
back_screen: 'product',
|
||||
next_screen: 'receipt',
|
||||
show_leftpane: false,
|
||||
show_numpad: false,
|
||||
init: function(parent, options) {
|
||||
var self = this;
|
||||
this._super(parent,options);
|
||||
this._super(parent, options);
|
||||
|
||||
this.pos.bind('change:selectedOrder',function(){
|
||||
this.bind_events();
|
||||
this.renderElement();
|
||||
this.watch_order_changes();
|
||||
},this);
|
||||
this.watch_order_changes();
|
||||
|
||||
this.bind_events();
|
||||
|
||||
this.line_delete_handler = function(event){
|
||||
var node = this;
|
||||
while(node && !node.classList.contains('paymentline')){
|
||||
node = node.parentNode;
|
||||
}
|
||||
if(node){
|
||||
self.pos.get('selectedOrder').removePaymentline(node.line)
|
||||
}
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
this.line_change_handler = function(event){
|
||||
var node = this;
|
||||
while(node && !node.classList.contains('paymentline')){
|
||||
node = node.parentNode;
|
||||
}
|
||||
if(node){
|
||||
node.line.set_amount(this.value);
|
||||
}
|
||||
};
|
||||
|
||||
this.line_click_handler = function(event){
|
||||
var node = this;
|
||||
while(node && !node.classList.contains('paymentline')){
|
||||
node = node.parentNode;
|
||||
}
|
||||
if(node){
|
||||
self.pos.get('selectedOrder').selectPaymentline(node.line);
|
||||
}
|
||||
};
|
||||
|
||||
this.hotkey_handler = function(event){
|
||||
if(event.which === 13){
|
||||
this.inputbuffer = "";
|
||||
this.firstinput = true;
|
||||
this.keyboard_handler = function(event){
|
||||
var key = '';
|
||||
if ( event.keyCode === 13 ) { // Enter
|
||||
self.validate_order();
|
||||
}else if(event.which === 27){
|
||||
self.back();
|
||||
} else if ( event.keyCode === 190 ) { // Dot
|
||||
key = '.';
|
||||
} else if ( event.keyCode === 46 ) { // Delete
|
||||
key = 'CLEAR';
|
||||
} else if ( event.keyCode === 8 ) { // Backspace
|
||||
key = 'BACKSPACE';
|
||||
event.preventDefault(); // Prevents history back nav
|
||||
} else if ( event.keyCode >= 48 && event.keyCode <= 57 ){ // Numbers
|
||||
key = '' + (event.keyCode - 48);
|
||||
} else if ( event.keyCode >= 96 && event.keyCode <= 105 ){ // Numpad Numbers
|
||||
key = '' + (event.keyCode - 96);
|
||||
} else if ( event.keyCode === 189 || event.keyCode === 109 ) { // Minus
|
||||
key = '-';
|
||||
} else if ( event.keyCode === 107 ) { // Plus
|
||||
key = '+';
|
||||
}
|
||||
|
||||
self.payment_input(key);
|
||||
|
||||
};
|
||||
},
|
||||
// resets the current input buffer
|
||||
reset_input: function(){
|
||||
var line = this.pos.get_order().selected_paymentline;
|
||||
this.firstinput = true;
|
||||
if (line) {
|
||||
this.inputbuffer = this.format_currency_no_symbol(line.get_amount());
|
||||
} else {
|
||||
this.inputbuffer = "";
|
||||
}
|
||||
},
|
||||
// handle both keyboard and numpad input. Accepts
|
||||
// a string that represents the key pressed.
|
||||
payment_input: function(input) {
|
||||
var oldbuf = this.inputbuffer.slice(0);
|
||||
|
||||
if (input === '.') {
|
||||
if (this.firstinput) {
|
||||
this.inputbuffer = "0.";
|
||||
}else if (!this.inputbuffer.length || this.inputbuffer === '-') {
|
||||
this.inputbuffer += "0.";
|
||||
} else if (this.inputbuffer.indexOf('.') < 0){
|
||||
this.inputbuffer = this.inputbuffer + '.';
|
||||
}
|
||||
} else if (input === 'CLEAR') {
|
||||
this.inputbuffer = "";
|
||||
} else if (input === 'BACKSPACE') {
|
||||
this.inputbuffer = this.inputbuffer.substring(0,this.inputbuffer.length - 1);
|
||||
} else if (input === '+') {
|
||||
if ( this.inputbuffer[0] === '-' ) {
|
||||
this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
|
||||
}
|
||||
} else if (input === '-') {
|
||||
if ( this.inputbuffer[0] === '-' ) {
|
||||
this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
|
||||
} else {
|
||||
this.inputbuffer = '-' + this.inputbuffer;
|
||||
}
|
||||
} else if (input[0] === '+' && !isNaN(parseFloat(input))) {
|
||||
this.inputbuffer = '' + ((parseFloat(this.inputbuffer) || 0) + parseFloat(input));
|
||||
} else if (!isNaN(parseInt(input))) {
|
||||
if (this.firstinput) {
|
||||
this.inputbuffer = '' + input;
|
||||
} else {
|
||||
this.inputbuffer += input;
|
||||
}
|
||||
}
|
||||
|
||||
this.firstinput = false;
|
||||
|
||||
if (this.inputbuffer !== oldbuf) {
|
||||
var order = this.pos.get_order();
|
||||
if (order.selected_paymentline) {
|
||||
order.selected_paymentline.set_amount(parseFloat(this.inputbuffer));
|
||||
this.order_changes();
|
||||
this.render_paymentlines();
|
||||
this.$('.paymentline.selected .edit').text(this.inputbuffer);
|
||||
}
|
||||
}
|
||||
},
|
||||
click_numpad: function(button) {
|
||||
this.payment_input(button.data('action'));
|
||||
},
|
||||
render_numpad: function() {
|
||||
var self = this;
|
||||
var numpad = $(QWeb.render('PaymentScreen-Numpad', { widget:this }));
|
||||
numpad.on('click','button',function(){
|
||||
self.click_numpad($(this));
|
||||
});
|
||||
return numpad;
|
||||
},
|
||||
click_delete_paymentline: function(cid){
|
||||
var lines = this.pos.get_order().get('paymentLines').models;
|
||||
for ( var i = 0; i < lines.length; i++ ) {
|
||||
if (lines[i].cid === cid) {
|
||||
this.pos.get_order().removePaymentline(lines[i]);
|
||||
this.reset_input();
|
||||
this.render_paymentlines();
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
click_paymentline: function(cid){
|
||||
var lines = this.pos.get_order().get('paymentLines').models;
|
||||
for ( var i = 0; i < lines.length; i++ ) {
|
||||
if (lines[i].cid === cid) {
|
||||
this.pos.get_order().selectPaymentline(lines[i]);
|
||||
this.reset_input();
|
||||
this.render_paymentlines();
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
render_paymentlines: function() {
|
||||
var self = this;
|
||||
var order = this.pos.get_order();
|
||||
var lines = order.get('paymentLines').models;
|
||||
|
||||
this.$('.paymentlines-container').empty();
|
||||
var lines = $(QWeb.render('PaymentScreen-Paymentlines', {
|
||||
widget: this,
|
||||
order: order,
|
||||
paymentlines: lines,
|
||||
}));
|
||||
|
||||
lines.on('click','.delete-button',function(){
|
||||
self.click_delete_paymentline($(this).data('cid'));
|
||||
});
|
||||
|
||||
lines.on('click','.paymentline',function(){
|
||||
self.click_paymentline($(this).data('cid'));
|
||||
});
|
||||
|
||||
lines.appendTo(this.$('.paymentlines-container'));
|
||||
},
|
||||
click_paymentmethods: function(id) {
|
||||
var cashregister = null;
|
||||
for ( var i = 0; i < this.pos.cashregisters.length; i++ ) {
|
||||
if ( this.pos.cashregisters[i].journal_id[0] === id ){
|
||||
cashregister = this.pos.cashregisters[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.pos.get_order().addPaymentline( cashregister );
|
||||
this.reset_input();
|
||||
this.render_paymentlines();
|
||||
},
|
||||
render_paymentmethods: function() {
|
||||
var self = this;
|
||||
var methods = $(QWeb.render('PaymentScreen-Paymentmethods', { widget:this }));
|
||||
methods.on('click','.paymentmethod',function(){
|
||||
self.click_paymentmethods($(this).data('id'));
|
||||
});
|
||||
return methods;
|
||||
},
|
||||
click_invoice: function(){
|
||||
var order = this.pos.get_order();
|
||||
order.set_to_invoice(!order.is_to_invoice());
|
||||
if (order.is_to_invoice()) {
|
||||
this.$('.js_invoice').addClass('highlight');
|
||||
} else {
|
||||
this.$('.js_invoice').removeClass('highlight');
|
||||
}
|
||||
},
|
||||
renderElement: function() {
|
||||
var self = this;
|
||||
this._super();
|
||||
|
||||
var numpad = this.render_numpad();
|
||||
numpad.appendTo(this.$('.payment-numpad'));
|
||||
|
||||
var methods = this.render_paymentmethods();
|
||||
methods.appendTo(this.$('.paymentmethods-container'));
|
||||
|
||||
this.render_paymentlines();
|
||||
|
||||
this.$('.back').click(function(){
|
||||
self.pos_widget.screen_selector.back();
|
||||
});
|
||||
|
||||
this.$('.next').click(function(){
|
||||
self.validate_order();
|
||||
});
|
||||
|
||||
this.$('.js_invoice').click(function(){
|
||||
self.click_invoice();
|
||||
});
|
||||
|
||||
},
|
||||
show: function(){
|
||||
this.pos.get_order().clean_empty_paymentlines();
|
||||
this.reset_input();
|
||||
this.render_paymentlines();
|
||||
this.order_changes();
|
||||
window.document.body.addEventListener('keydown',this.keyboard_handler);
|
||||
this._super();
|
||||
},
|
||||
hide: function(){
|
||||
window.document.body.removeEventListener('keydown',this.keyboard_handler);
|
||||
this._super();
|
||||
},
|
||||
// sets up listeners to watch for order changes
|
||||
watch_order_changes: function() {
|
||||
var self = this;
|
||||
|
||||
this.enable_numpad();
|
||||
this.focus_selected_line();
|
||||
|
||||
document.body.addEventListener('keyup', this.hotkey_handler);
|
||||
|
||||
this.add_action_button({
|
||||
label: _t('Back'),
|
||||
icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png',
|
||||
click: function(){
|
||||
self.back();
|
||||
},
|
||||
});
|
||||
|
||||
this.add_action_button({
|
||||
label: _t('Validate'),
|
||||
name: 'validation',
|
||||
icon: '/point_of_sale/static/src/img/icons/png48/validate.png',
|
||||
click: function(){
|
||||
self.validate_order();
|
||||
},
|
||||
});
|
||||
|
||||
if( this.pos.config.iface_invoicing ){
|
||||
this.add_action_button({
|
||||
label: 'Invoice',
|
||||
name: 'invoice',
|
||||
icon: '/point_of_sale/static/src/img/icons/png48/invoice.png',
|
||||
click: function(){
|
||||
self.validate_order({invoice: true});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if( this.pos.config.iface_cashdrawer ){
|
||||
this.add_action_button({
|
||||
label: _t('Cash'),
|
||||
name: 'cashbox',
|
||||
icon: '/point_of_sale/static/src/img/open-cashbox.png',
|
||||
click: function(){
|
||||
self.pos.proxy.open_cashbox();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.update_payment_summary();
|
||||
|
||||
},
|
||||
close: function(){
|
||||
this._super();
|
||||
this.disable_numpad();
|
||||
document.body.removeEventListener('keyup',this.hotkey_handler);
|
||||
},
|
||||
remove_empty_lines: function(){
|
||||
var order = this.pos.get('selectedOrder');
|
||||
var lines = order.get('paymentLines').models.slice(0);
|
||||
for(var i = 0; i < lines.length; i++){
|
||||
var line = lines[i];
|
||||
if(line.get_amount() === 0){
|
||||
order.removePaymentline(line);
|
||||
}
|
||||
}
|
||||
},
|
||||
back: function() {
|
||||
this.remove_empty_lines();
|
||||
this.pos_widget.screen_selector.set_current_screen(this.back_screen);
|
||||
},
|
||||
bind_events: function() {
|
||||
var order = this.pos.get_order();
|
||||
if(this.old_order){
|
||||
this.old_order.unbind(null,null,this);
|
||||
}
|
||||
var order = this.pos.get('selectedOrder');
|
||||
order.bind('change:selected_paymentline',this.focus_selected_line,this);
|
||||
|
||||
order.bind('all',function(){
|
||||
self.order_changes();
|
||||
});
|
||||
this.old_order = order;
|
||||
|
||||
if(this.old_paymentlines){
|
||||
this.old_paymentlines.unbind(null,null,this);
|
||||
}
|
||||
var paymentlines = order.get('paymentLines');
|
||||
paymentlines.bind('add', this.add_paymentline, this);
|
||||
paymentlines.bind('change:selected', this.rerender_paymentline, this);
|
||||
paymentlines.bind('change:amount', function(line){
|
||||
if(!line.selected && line.node){
|
||||
line.node.value = line.amount.toFixed(2);
|
||||
}
|
||||
this.update_payment_summary();
|
||||
},this);
|
||||
paymentlines.bind('remove', this.remove_paymentline, this);
|
||||
paymentlines.bind('all', this.update_payment_summary, this);
|
||||
|
||||
this.old_paymentlines = paymentlines;
|
||||
|
||||
if(this.old_orderlines){
|
||||
this.old_orderlines.unbind(null,null,this);
|
||||
}
|
||||
var orderlines = order.get('orderLines');
|
||||
orderlines.bind('all', this.update_payment_summary, this);
|
||||
|
||||
this.old_orderlines = orderlines;
|
||||
},
|
||||
focus_selected_line: function(){
|
||||
var line = this.pos.get('selectedOrder').selected_paymentline;
|
||||
if(line){
|
||||
var input = line.node.querySelector('input');
|
||||
if(!input){
|
||||
return;
|
||||
}
|
||||
var value = input.value;
|
||||
input.focus();
|
||||
|
||||
if(this.numpad_state){
|
||||
this.numpad_state.reset();
|
||||
}
|
||||
|
||||
if(Number(value) === 0){
|
||||
input.value = '';
|
||||
}else{
|
||||
input.value = value;
|
||||
input.select();
|
||||
}
|
||||
}
|
||||
},
|
||||
add_paymentline: function(line) {
|
||||
var list_container = this.el.querySelector('.payment-lines');
|
||||
list_container.appendChild(this.render_paymentline(line));
|
||||
|
||||
if(this.numpad_state){
|
||||
this.numpad_state.reset();
|
||||
}
|
||||
},
|
||||
render_paymentline: function(line){
|
||||
var el_html = openerp.qweb.render('Paymentline',{widget: this, line: line});
|
||||
el_html = _.str.trim(el_html);
|
||||
|
||||
var el_node = document.createElement('tbody');
|
||||
el_node.innerHTML = el_html;
|
||||
el_node = el_node.childNodes[0];
|
||||
el_node.line = line;
|
||||
el_node.querySelector('.paymentline-delete')
|
||||
.addEventListener('click', this.line_delete_handler);
|
||||
el_node.addEventListener('click', this.line_click_handler);
|
||||
el_node.querySelector('input')
|
||||
.addEventListener('keyup', this.line_change_handler);
|
||||
|
||||
line.node = el_node;
|
||||
|
||||
return el_node;
|
||||
},
|
||||
rerender_paymentline: function(line){
|
||||
var old_node = line.node;
|
||||
var new_node = this.render_paymentline(line);
|
||||
|
||||
old_node.parentNode.replaceChild(new_node,old_node);
|
||||
},
|
||||
remove_paymentline: function(line){
|
||||
line.node.parentNode.removeChild(line.node);
|
||||
line.node = undefined;
|
||||
},
|
||||
renderElement: function(){
|
||||
this._super();
|
||||
|
||||
var paymentlines = this.pos.get('selectedOrder').get('paymentLines').models;
|
||||
var list_container = this.el.querySelector('.payment-lines');
|
||||
|
||||
for(var i = 0; i < paymentlines.length; i++){
|
||||
list_container.appendChild(this.render_paymentline(paymentlines[i]));
|
||||
}
|
||||
|
||||
this.update_payment_summary();
|
||||
},
|
||||
update_payment_summary: function() {
|
||||
var currentOrder = this.pos.get('selectedOrder');
|
||||
var paidTotal = currentOrder.getPaidTotal();
|
||||
var dueTotal = currentOrder.getTotalTaxIncluded();
|
||||
var remaining = dueTotal > paidTotal ? dueTotal - paidTotal : 0;
|
||||
var change = paidTotal > dueTotal ? paidTotal - dueTotal : 0;
|
||||
|
||||
this.$('.payment-due-total').html(this.format_currency(dueTotal));
|
||||
this.$('.payment-paid-total').html(this.format_currency(paidTotal));
|
||||
this.$('.payment-remaining').html(this.format_currency(remaining));
|
||||
this.$('.payment-change').html(this.format_currency(change));
|
||||
if(currentOrder.selected_orderline === undefined){
|
||||
remaining = 1; // What is this ?
|
||||
}
|
||||
|
||||
if(this.pos_widget.action_bar){
|
||||
this.pos_widget.action_bar.set_button_disabled('validation', !this.is_paid());
|
||||
this.pos_widget.action_bar.set_button_disabled('invoice', !this.is_paid());
|
||||
}
|
||||
},
|
||||
is_paid: function(){
|
||||
var currentOrder = this.pos.get('selectedOrder');
|
||||
return (currentOrder.getTotalTaxIncluded() < 0.000001
|
||||
|| currentOrder.getPaidTotal() + 0.000001 >= currentOrder.getTotalTaxIncluded());
|
||||
|
||||
},
|
||||
validate_order: function(options) {
|
||||
// called when the order is changed, used to show if
|
||||
// the order is paid or not
|
||||
order_changes: function(){
|
||||
var self = this;
|
||||
var order = this.pos.get_order();
|
||||
if (order.isPaid()) {
|
||||
self.$('.next').addClass('highlight');
|
||||
}else{
|
||||
self.$('.next').removeClass('highlight');
|
||||
}
|
||||
},
|
||||
// Check if the order is paid, then sends it to the backend,
|
||||
// and complete the sale process
|
||||
validate_order: function() {
|
||||
var self = this;
|
||||
options = options || {};
|
||||
|
||||
var currentOrder = this.pos.get('selectedOrder');
|
||||
var order = this.pos.get_order();
|
||||
|
||||
if(!this.is_paid()){
|
||||
if (!order.isPaid() || this.invoicing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( this.pos.config.iface_cashdrawer
|
||||
&& this.pos.get('selectedOrder').get('paymentLines').find( function(pl){
|
||||
return pl.cashregister.journal.type === 'cash';
|
||||
})){
|
||||
if (order.isPaidWithCash() && this.pos.config.iface_cashdrawer) {
|
||||
this.pos.proxy.open_cashbox();
|
||||
}
|
||||
|
||||
if(options.invoice){
|
||||
// deactivate the validation button while we try to send the order
|
||||
this.pos_widget.action_bar.set_button_disabled('validation',true);
|
||||
this.pos_widget.action_bar.set_button_disabled('invoice',true);
|
||||
|
||||
var invoiced = this.pos.push_and_invoice_order(currentOrder);
|
||||
if (order.is_to_invoice()) {
|
||||
var invoiced = this.pos.push_and_invoice_order(order);
|
||||
this.invoicing = true;
|
||||
|
||||
invoiced.fail(function(error){
|
||||
if(error === 'error-no-client'){
|
||||
self.pos_widget.screen_selector.show_popup('error-no-client');
|
||||
}else{
|
||||
self.invoicing = false;
|
||||
if (error === 'error-no-client') {
|
||||
self.pos_widget.screen_selector.show_popup('confirm',{
|
||||
message: _t('Please select the Customer'),
|
||||
comment: _t('You need to select the customer before you can invoice an order.'),
|
||||
confirm: function(){
|
||||
self.pos_widget.screen_selector.set_current_screen('clientlist');
|
||||
},
|
||||
});
|
||||
} else {
|
||||
self.pos_widget.screen_selector.show_popup('error-invoice-transfer');
|
||||
}
|
||||
self.pos_widget.action_bar.set_button_disabled('validation',false);
|
||||
self.pos_widget.action_bar.set_button_disabled('invoice',false);
|
||||
});
|
||||
|
||||
invoiced.done(function(){
|
||||
self.pos_widget.action_bar.set_button_disabled('validation',false);
|
||||
self.pos_widget.action_bar.set_button_disabled('invoice',false);
|
||||
self.pos.get('selectedOrder').destroy();
|
||||
self.invoicing = false;
|
||||
order.finalize();
|
||||
});
|
||||
|
||||
}else{
|
||||
this.pos.push_order(currentOrder)
|
||||
if(this.pos.config.iface_print_via_proxy){
|
||||
} else {
|
||||
this.pos.push_order(order)
|
||||
if (this.pos.config.iface_print_via_proxy) {
|
||||
var receipt = currentOrder.export_for_printing();
|
||||
this.pos.proxy.print_receipt(QWeb.render('XmlReceipt',{
|
||||
receipt: receipt, widget: self,
|
||||
}));
|
||||
this.pos.get('selectedOrder').destroy(); //finish order and go back to scan screen
|
||||
}else{
|
||||
order.finalize(); //finish order and go back to scan screen
|
||||
} else {
|
||||
this.pos_widget.screen_selector.set_current_screen(this.next_screen);
|
||||
}
|
||||
}
|
||||
|
||||
// hide onscreen (iOS) keyboard
|
||||
setTimeout(function(){
|
||||
document.activeElement.blur();
|
||||
$("input").blur();
|
||||
},250);
|
||||
},
|
||||
enable_numpad: function(){
|
||||
this.disable_numpad(); //ensure we don't register the callbacks twice
|
||||
this.numpad_state = this.pos_widget.numpad.state;
|
||||
if(this.numpad_state){
|
||||
this.numpad_state.reset();
|
||||
this.numpad_state.changeMode('payment');
|
||||
this.numpad_state.bind('set_value', this.set_value, this);
|
||||
this.numpad_state.bind('change:mode', this.set_mode_back_to_payment, this);
|
||||
}
|
||||
|
||||
},
|
||||
disable_numpad: function(){
|
||||
if(this.numpad_state){
|
||||
this.numpad_state.unbind('set_value', this.set_value);
|
||||
this.numpad_state.unbind('change:mode',this.set_mode_back_to_payment);
|
||||
}
|
||||
},
|
||||
set_mode_back_to_payment: function() {
|
||||
this.numpad_state.set({mode: 'payment'});
|
||||
},
|
||||
set_value: function(val) {
|
||||
var selected_line =this.pos.get('selectedOrder').selected_paymentline;
|
||||
if(selected_line){
|
||||
selected_line.set_amount(val);
|
||||
selected_line.node.querySelector('input').value = selected_line.amount.toFixed(2);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -1,93 +1,89 @@
|
||||
function openerp_pos_tests(instance, module){ //module is instance.point_of_sale
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Various UI Tests to measure performance and memory leaks.
|
||||
module.UiTester = function(){
|
||||
var running = false;
|
||||
var queue = new module.JobQueue();
|
||||
openerp.Tour.register({
|
||||
id: 'pos_basic_order',
|
||||
name: 'Complete a basic order trough the Front-End',
|
||||
path: '/web#model=pos.session.opening&action=point_of_sale.action_pos_session_opening',
|
||||
mode: 'test',
|
||||
steps: [
|
||||
{
|
||||
title: 'Wait fot the bloody screen to be ready',
|
||||
wait: 200,
|
||||
},
|
||||
{
|
||||
title: 'Load the Session',
|
||||
waitNot: '.oe_loading:visible',
|
||||
element: 'span:contains("Resume Session"),span:contains("Start Session")',
|
||||
},
|
||||
{
|
||||
title: 'Loading the Loading Screen',
|
||||
waitFor: '.loader'
|
||||
},
|
||||
{
|
||||
title: 'Waiting for the end of loading...',
|
||||
waitFor: '.loader:hidden',
|
||||
},
|
||||
{
|
||||
title: 'Loading The Point of Sale',
|
||||
waitFor: '.pos',
|
||||
},
|
||||
{
|
||||
title: 'On va manger des CHIPS!',
|
||||
element: '.product-list .product-name:contains("250g Lays Pickels")',
|
||||
},
|
||||
{
|
||||
title: 'The chips have been added to the Order',
|
||||
waitFor: '.order .product-name:contains("250g Lays Pickels")',
|
||||
},
|
||||
{
|
||||
title: 'The order total has been updated to the correct value',
|
||||
wait: 2000,
|
||||
waitFor: '.order .total .value:contains("1.48 €")',
|
||||
},
|
||||
{
|
||||
title: "Let's buy more chips",
|
||||
element: '.product-list .product-name:contains("250g Lays Pickels")',
|
||||
},
|
||||
{
|
||||
title: "Let's veryify we pay the correct price for two bags of chips",
|
||||
waitFor: '.order .total .value:contains("2.96 €")',
|
||||
},
|
||||
{
|
||||
title: "Let's pay with a debit card",
|
||||
element: ".paypad-button:contains('Bank')",
|
||||
},
|
||||
{
|
||||
title: "Let's accept the payment",
|
||||
onload: function(){
|
||||
// The test cannot validate or cancel the print() ... so we replace it by a noop !.
|
||||
window._print = window.print;
|
||||
window.print = function(){ console.log('Print!') };
|
||||
},
|
||||
element: ".button .iconlabel:contains('Validate'):visible",
|
||||
},
|
||||
{
|
||||
title: "Let's finish the order",
|
||||
element: ".button:not(.disabled) .iconlabel:contains('Next'):visible",
|
||||
},
|
||||
{
|
||||
onload: function(){
|
||||
window.print = window._print;
|
||||
window._print = undefined;
|
||||
},
|
||||
title: "Let's wait for the order posting",
|
||||
waitFor: ".oe_status.js_synch .js_connected:visible",
|
||||
},
|
||||
{
|
||||
title: "Let's close the Point of Sale",
|
||||
element: ".header-button:contains('Close')",
|
||||
},
|
||||
{
|
||||
title: "Wait for the backend to ready itself",
|
||||
element: 'span:contains("Resume Session"),span:contains("Start Session")',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// stop the currently running test
|
||||
this.stop = function(){
|
||||
queue.clear();
|
||||
};
|
||||
})();
|
||||
|
||||
// randomly switch product categories
|
||||
this.category_switch = function(interval){
|
||||
queue.schedule(function(){
|
||||
var breadcrumbs = $('.breadcrumb-button');
|
||||
var categories = $('.category-button');
|
||||
if(categories.length > 0){
|
||||
var rnd = Math.floor(Math.random()*categories.length);
|
||||
categories.eq(rnd).click();
|
||||
}else{
|
||||
var rnd = Math.floor(Math.random()*breadcrumbs.length);
|
||||
breadcrumbs.eq(rnd).click();
|
||||
}
|
||||
},{repeat:true, duration:interval});
|
||||
};
|
||||
|
||||
// randomly order products then resets the order
|
||||
this.order_products = function(interval){
|
||||
|
||||
queue.schedule(function(){
|
||||
var def = new $.Deferred();
|
||||
var order_queue = new module.JobQueue();
|
||||
var order_size = 1 + Math.floor(Math.random()*10);
|
||||
|
||||
while(order_size--){
|
||||
order_queue.schedule(function(){
|
||||
var products = $('.product');
|
||||
if(products.length > 0){
|
||||
var rnd = Math.floor(Math.random()*products.length);
|
||||
products.eq(rnd).click();
|
||||
}
|
||||
},{duration:20});
|
||||
}
|
||||
order_queue.finished().then(function(){
|
||||
$('.deleteorder-button').click();
|
||||
def.resolve();
|
||||
});
|
||||
return def;
|
||||
},{repeat:true, duration: interval});
|
||||
|
||||
};
|
||||
|
||||
// makes a complete product order cycle ( print via proxy must be activated, and scale deactivated )
|
||||
this.full_order_cycle = function(interval){
|
||||
queue.schedule(function(){
|
||||
var def = new $.Deferred();
|
||||
var order_queue = new module.JobQueue();
|
||||
var order_size = 1 + Math.floor(Math.random()*50);
|
||||
|
||||
while(order_size--){
|
||||
order_queue.schedule(function(){
|
||||
var products = $('.product');
|
||||
if(products.length > 0){
|
||||
var rnd = Math.floor(Math.random()*products.length);
|
||||
products.eq(rnd).click();
|
||||
}
|
||||
},{duration:50});
|
||||
}
|
||||
order_queue.schedule(function(){
|
||||
$('.paypad-button:first').click();
|
||||
},{duration:250});
|
||||
order_queue.schedule(function(){
|
||||
$('.paymentline-input:first').val(10000);
|
||||
$('.paymentline-input:first').keydown();
|
||||
$('.paymentline-input:first').keyup();
|
||||
},{duration:250});
|
||||
order_queue.schedule(function(){
|
||||
$('.pos-actionbar-button-list .button:eq(2)').click();
|
||||
},{duration:250});
|
||||
order_queue.schedule(function(){
|
||||
def.resolve();
|
||||
});
|
||||
return def;
|
||||
},{repeat: true, duration: interval});
|
||||
};
|
||||
};
|
||||
|
||||
if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){
|
||||
window.pos_test_ui = new module.UiTester();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
function openerp_pos_basewidget(instance, module){ //module is instance.point_of_sale
|
||||
|
||||
var round_pr = instance.web.round_precision
|
||||
|
||||
// This is a base class for all Widgets in the POS. It exposes relevant data to the
|
||||
// templates :
|
||||
// - widget.currency : { symbol: '$' | '€' | ..., position: 'before' | 'after }
|
||||
@@ -27,6 +29,12 @@ function openerp_pos_basewidget(instance, module){ //module is instance.point_of
|
||||
|
||||
var decimals = Math.max(0,Math.ceil(Math.log(1.0 / this.currency.rounding) / Math.log(10)));
|
||||
|
||||
this.format_currency_no_symbol = function(amount){
|
||||
amount = round_pr(amount,this.currency.rounding);
|
||||
amount = amount.toFixed(decimals);
|
||||
return amount;
|
||||
};
|
||||
|
||||
this.format_currency = function(amount){
|
||||
if(typeof amount === 'number'){
|
||||
amount = Math.round(amount*100)/100;
|
||||
@@ -37,7 +45,7 @@ function openerp_pos_basewidget(instance, module){ //module is instance.point_of
|
||||
}else{
|
||||
return this.currency.symbol + ' ' + amount;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
},
|
||||
show: function(){
|
||||
|
||||
@@ -83,46 +83,21 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
||||
},
|
||||
});
|
||||
|
||||
// The paypad allows to select the payment method (cashregisters)
|
||||
// used to pay the order.
|
||||
module.PaypadWidget = module.PosBaseWidget.extend({
|
||||
template: 'PaypadWidget',
|
||||
// The action pads contains the payment button and the customer selection button.
|
||||
module.ActionpadWidget = module.PosBaseWidget.extend({
|
||||
template: 'ActionpadWidget',
|
||||
renderElement: function() {
|
||||
var self = this;
|
||||
this._super();
|
||||
|
||||
_.each(this.pos.cashregisters,function(cashregister) {
|
||||
var button = new module.PaypadButtonWidget(self,{
|
||||
pos: self.pos,
|
||||
pos_widget : self.pos_widget,
|
||||
cashregister: cashregister,
|
||||
});
|
||||
button.appendTo(self.$el);
|
||||
this.$('.pay').click(function(){
|
||||
self.pos.pos_widget.screen_selector.set_current_screen('payment');
|
||||
});
|
||||
this.$('.set-customer').click(function(){
|
||||
self.pos.pos_widget.screen_selector.set_current_screen('clientlist');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.PaypadButtonWidget = module.PosBaseWidget.extend({
|
||||
template: 'PaypadButtonWidget',
|
||||
init: function(parent, options){
|
||||
this._super(parent, options);
|
||||
this.cashregister = options.cashregister;
|
||||
},
|
||||
renderElement: function() {
|
||||
var self = this;
|
||||
this._super();
|
||||
|
||||
this.$el.click(function(){
|
||||
if (self.pos.get('selectedOrder').get('screen') === 'receipt'){ //TODO Why ?
|
||||
console.warn('TODO should not get there...?');
|
||||
return;
|
||||
}
|
||||
self.pos.get('selectedOrder').addPaymentline(self.cashregister);
|
||||
self.pos_widget.screen_selector.set_current_screen('payment');
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
module.OrderWidget = module.PosBaseWidget.extend({
|
||||
template:'OrderWidget',
|
||||
init: function(parent, options) {
|
||||
@@ -318,90 +293,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
||||
},
|
||||
});
|
||||
|
||||
module.ActionButtonWidget = instance.web.Widget.extend({
|
||||
template:'ActionButtonWidget',
|
||||
icon_template:'ActionButtonWidgetWithIcon',
|
||||
init: function(parent, options){
|
||||
this._super(parent, options);
|
||||
this.label = options.label || 'button';
|
||||
this.rightalign = options.rightalign || false;
|
||||
this.click_action = options.click;
|
||||
this.disabled = options.disabled || false;
|
||||
if(options.icon){
|
||||
this.icon = options.icon;
|
||||
this.template = this.icon_template;
|
||||
}
|
||||
},
|
||||
set_disabled: function(disabled){
|
||||
if(this.disabled != disabled){
|
||||
this.disabled = !!disabled;
|
||||
this.renderElement();
|
||||
}
|
||||
},
|
||||
renderElement: function(){
|
||||
this._super();
|
||||
if(this.click_action && !this.disabled){
|
||||
this.$el.click(_.bind(this.click_action, this));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
module.ActionBarWidget = instance.web.Widget.extend({
|
||||
template:'ActionBarWidget',
|
||||
init: function(parent, options){
|
||||
this._super(parent,options);
|
||||
this.button_list = [];
|
||||
this.buttons = {};
|
||||
this.visibility = {};
|
||||
},
|
||||
set_element_visible: function(element, visible, action){
|
||||
if(visible != this.visibility[element]){
|
||||
this.visibility[element] = !!visible;
|
||||
if(visible){
|
||||
this.$('.'+element).removeClass('oe_hidden');
|
||||
}else{
|
||||
this.$('.'+element).addClass('oe_hidden');
|
||||
}
|
||||
}
|
||||
if(visible && action){
|
||||
this.action[element] = action;
|
||||
this.$('.'+element).off('click').click(action);
|
||||
}
|
||||
},
|
||||
set_button_disabled: function(name, disabled){
|
||||
var b = this.buttons[name];
|
||||
if(b){
|
||||
b.set_disabled(disabled);
|
||||
}
|
||||
},
|
||||
destroy_buttons:function(){
|
||||
for(var i = 0; i < this.button_list.length; i++){
|
||||
this.button_list[i].destroy();
|
||||
}
|
||||
this.button_list = [];
|
||||
this.buttons = {};
|
||||
return this;
|
||||
},
|
||||
get_button_count: function(){
|
||||
return this.button_list.length;
|
||||
},
|
||||
add_new_button: function(button_options){
|
||||
var button = new module.ActionButtonWidget(this,button_options);
|
||||
this.button_list.push(button);
|
||||
if(button_options.name){
|
||||
this.buttons[button_options.name] = button;
|
||||
}
|
||||
button.appendTo(this.$('.pos-actionbar-button-list'));
|
||||
return button;
|
||||
},
|
||||
show:function(){
|
||||
this.$el.removeClass('oe_hidden');
|
||||
},
|
||||
hide:function(){
|
||||
this.$el.addClass('oe_hidden');
|
||||
},
|
||||
});
|
||||
|
||||
module.ProductCategoriesWidget = module.PosBaseWidget.extend({
|
||||
template: 'ProductCategoriesWidget',
|
||||
init: function(parent, options){
|
||||
@@ -797,6 +688,19 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
||||
this.$('.button.reference').click(function(){
|
||||
self.pos.barcode_reader.scan(self.$('input.ean').val());
|
||||
});
|
||||
this.$('.button.show_orders').click(function(){
|
||||
self.pos.pos_widget.screen_selector.show_popup('unsent-orders');
|
||||
});
|
||||
this.$('.button.delete_orders').click(function(){
|
||||
self.pos.pos_widget.screen_selector.show_popup('confirm',{
|
||||
message: _t('Delete Unsent Orders ?'),
|
||||
comment: _t('This operation will permanently destroy all unsent orders from the local storage. You will lose all the data. This operation cannot be undone.'),
|
||||
confirm: function(){
|
||||
self.pos.db.remove_all_orders();
|
||||
self.pos.set({synch: { state:'connected', pending: 0 }});
|
||||
},
|
||||
});
|
||||
});
|
||||
_.each(this.eans, function(ean, name){
|
||||
self.$('.button.'+name).click(function(){
|
||||
self.$('input.ean').val(ean);
|
||||
@@ -840,7 +744,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
||||
self.set_status(synch.state, synch.pending);
|
||||
});
|
||||
this.$el.click(function(){
|
||||
self.pos.flush();
|
||||
self.pos.push_order();
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -903,7 +807,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
||||
// - a header, containing the list of orders
|
||||
// - a leftpane, containing the list of bought products (orderlines)
|
||||
// - a rightpane, containing the screens (see pos_screens.js)
|
||||
// - an actionbar on the bottom, containing various action buttons
|
||||
// - popups
|
||||
// - an onscreen keyboard
|
||||
// a screen_selector which controls the switching between screens and the showing/closing of popups
|
||||
@@ -1001,23 +904,50 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
||||
self.screen_selector.show_popup('error', 'Sorry, we could not create a user session');
|
||||
}else if(!self.pos.config){
|
||||
self.screen_selector.show_popup('error', 'Sorry, we could not find any PoS Configuration for this session');
|
||||
}else if(self.pos.config.iface_fullscreen && document.body.webkitRequestFullscreen && (
|
||||
window.screen.availWidth > window.innerWidth ||
|
||||
window.screen.availHeight > window.innerHeight )){
|
||||
self.screen_selector.show_popup('fullscreen');
|
||||
}
|
||||
|
||||
self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').addClass('oe_hidden');});
|
||||
|
||||
self.pos.flush();
|
||||
self.pos.push_order();
|
||||
|
||||
}).fail(function(){ // error when loading models data from the backend
|
||||
return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_session_opening']], ['res_id'])
|
||||
.pipe( _.bind(function(res){
|
||||
return instance.session.rpc('/web/action/load', {'action_id': res[0]['res_id']})
|
||||
.pipe(_.bind(function(result){
|
||||
var action = result.result;
|
||||
this.do_action(action);
|
||||
}, this));
|
||||
}, self));
|
||||
}).fail(function(err){ // error when loading models data from the backend
|
||||
self.loading_error(err);
|
||||
});
|
||||
},
|
||||
loading_error: function(err){
|
||||
var self = this;
|
||||
|
||||
var message = err.message;
|
||||
var comment = err.stack;
|
||||
|
||||
if(err.message === 'XmlHttpRequestError '){
|
||||
message = 'Network Failure (XmlHttpRequestError)';
|
||||
comment = 'The Point of Sale could not be loaded due to a network problem.\n Please check your internet connection.';
|
||||
}else if(err.message === 'OpenERP Server Error'){
|
||||
message = err.data.message;
|
||||
comment = err.data.debug;
|
||||
}
|
||||
|
||||
if( typeof comment !== 'string' ){
|
||||
comment = 'Traceback not available.';
|
||||
}
|
||||
|
||||
var popup = $(QWeb.render('ErrorTracebackPopupWidget',{
|
||||
widget: { message: message, comment: comment },
|
||||
}));
|
||||
|
||||
popup.find('.button').click(function(){
|
||||
self.close();
|
||||
});
|
||||
|
||||
popup.css({ zindex: 9001 });
|
||||
|
||||
popup.appendTo(this.$el);
|
||||
},
|
||||
loading_progress: function(fac){
|
||||
this.$('.loader .loader-feedback').removeClass('oe_hidden');
|
||||
this.$('.loader .progress').css({'width': ''+Math.floor(fac*100)+'%'});
|
||||
@@ -1076,15 +1006,21 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
||||
this.choose_receipt_popup = new module.ChooseReceiptPopupWidget(this, {});
|
||||
this.choose_receipt_popup.appendTo(this.$el);
|
||||
|
||||
this.error_no_client_popup = new module.ErrorNoClientPopupWidget(this, {});
|
||||
this.error_no_client_popup.appendTo(this.$el);
|
||||
|
||||
this.error_invoice_transfer_popup = new module.ErrorInvoiceTransferPopupWidget(this, {});
|
||||
this.error_invoice_transfer_popup.appendTo(this.$el);
|
||||
|
||||
this.error_traceback_popup = new module.ErrorTracebackPopupWidget(this,{});
|
||||
this.error_traceback_popup.appendTo(this.$el);
|
||||
|
||||
this.confirm_popup = new module.ConfirmPopupWidget(this,{});
|
||||
this.confirm_popup.appendTo(this.$el);
|
||||
|
||||
this.fullscreen_popup = new module.FullscreenPopup(this,{});
|
||||
this.fullscreen_popup.appendTo(this.$el);
|
||||
|
||||
this.unsent_orders_popup = new module.UnsentOrdersPopupWidget(this,{});
|
||||
this.unsent_orders_popup.appendTo(this.$el);
|
||||
|
||||
// -------- Misc ---------
|
||||
|
||||
this.close_button = new module.HeaderButtonWidget(this,{
|
||||
@@ -1104,11 +1040,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
||||
this.username = new module.UsernameWidget(this,{});
|
||||
this.username.replace(this.$('.placeholder-UsernameWidget'));
|
||||
|
||||
this.action_bar = new module.ActionBarWidget(this);
|
||||
this.action_bar.replace(this.$(".placeholder-RightActionBar"));
|
||||
|
||||
this.paypad = new module.PaypadWidget(this, {});
|
||||
this.paypad.replace(this.$('.placeholder-PaypadWidget'));
|
||||
this.actionpad = new module.ActionpadWidget(this, {});
|
||||
this.actionpad.replace(this.$('.placeholder-ActionpadWidget'));
|
||||
|
||||
this.numpad = new module.NumpadWidget(this);
|
||||
this.numpad.replace(this.$('.placeholder-NumpadWidget'));
|
||||
@@ -1137,9 +1070,11 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
||||
'error-barcode': this.error_barcode_popup,
|
||||
'error-session': this.error_session_popup,
|
||||
'choose-receipt': this.choose_receipt_popup,
|
||||
'error-no-client': this.error_no_client_popup,
|
||||
'error-invoice-transfer': this.error_invoice_transfer_popup,
|
||||
'error-traceback': this.error_traceback_popup,
|
||||
'confirm': this.confirm_popup,
|
||||
'fullscreen': this.fullscreen_popup,
|
||||
'unsent-orders': this.unsent_orders_popup,
|
||||
},
|
||||
default_screen: 'products',
|
||||
default_mode: 'cashier',
|
||||
@@ -1164,10 +1099,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
|
||||
this.numpad_visible = visible;
|
||||
if(visible){
|
||||
this.numpad.show();
|
||||
this.paypad.show();
|
||||
this.actionpad.show();
|
||||
}else{
|
||||
this.numpad.hide();
|
||||
this.paypad.hide();
|
||||
this.actionpad.hide();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Binary file not shown.
@@ -36,19 +36,12 @@
|
||||
<div class='subwindow-container'>
|
||||
<div class='subwindow-container-fix pads'>
|
||||
<div class="control-buttons oe_hidden"></div>
|
||||
<div class="placeholder-PaypadWidget"></div>
|
||||
<div class="placeholder-ActionpadWidget"></div>
|
||||
<div class="placeholder-NumpadWidget"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='subwindow collapsed'>
|
||||
<div class='subwindow-container'>
|
||||
<div class='subwindow-container-fix'>
|
||||
<div class='placeholder-LeftActionBar'></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -60,14 +53,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='subwindow collapsed'>
|
||||
<div class='subwindow-container'>
|
||||
<div class='subwindow-container-fix'>
|
||||
<div class='placeholder-RightActionBar'></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -144,8 +129,14 @@
|
||||
<div>There are pending operations that could not be saved into the database, are you sure you want to exit?</div>
|
||||
</t>
|
||||
|
||||
<t t-name="PaypadWidget">
|
||||
<div class="paypad touch-scrollable">
|
||||
<t t-name="ActionpadWidget">
|
||||
<div class='actionpad'>
|
||||
<button class='button set-customer'>
|
||||
<i class='fa fa-user' /> Set Customer
|
||||
</button>
|
||||
<button class="button pay">
|
||||
<i class='fa fa-chevron-right' /> Payment
|
||||
</button>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -383,41 +374,179 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
|
||||
<t t-name="PaymentScreenWidget">
|
||||
<div class="payment-screen screen touch-scrollable">
|
||||
<div class="pos-payment-container">
|
||||
<div class='payment-due-total'></div>
|
||||
<div class='payment-lines'></div>
|
||||
<div class='payment-info'>
|
||||
<div class="infoline">
|
||||
<span class='left-block'>
|
||||
Paid:
|
||||
</span>
|
||||
<span class="right-block payment-paid-total"></span>
|
||||
</div>
|
||||
<div class="infoline">
|
||||
<span class='left-block'>
|
||||
Remaining:
|
||||
</span>
|
||||
<span class="right-block payment-remaining"></span>
|
||||
</div>
|
||||
<div class="infoline bigger" >
|
||||
<span class='left-block'>
|
||||
Change:
|
||||
</span>
|
||||
<span class="right-block payment-change"></span>
|
||||
</div>
|
||||
<t t-name="PaymentScreen-Paymentlines">
|
||||
<t t-if="!paymentlines.length">
|
||||
<div class='paymentlines-empty'>
|
||||
<div class='total'>
|
||||
<t t-esc="widget.format_currency(order.getTotalTaxIncluded())"/>
|
||||
</div>
|
||||
<div class='message'>
|
||||
Please select a payment method.
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-if="paymentlines.length">
|
||||
<table class='paymentlines'>
|
||||
<colgroup>
|
||||
<col class='due' />
|
||||
<col class='tendered' />
|
||||
<col class='change' />
|
||||
<col class='method' />
|
||||
<col class='controls' />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr class='label'>
|
||||
<th>Due</th>
|
||||
<th>Tendered</th>
|
||||
<th>Change</th>
|
||||
<th>Method</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach='paymentlines' t-as='line'>
|
||||
<t t-if='line.selected'>
|
||||
<tr class='paymentline selected'>
|
||||
<td class='col-due'> <t t-esc='widget.format_currency_no_symbol(order.getDueLeft(line))' /> </td>
|
||||
<td class='col-tendered edit'>
|
||||
<t t-esc='widget.inputbuffer' />
|
||||
<!-- <t t-esc='line.get_amount()' /> -->
|
||||
</td>
|
||||
<t t-if='order.getChange(line)'>
|
||||
<td class='col-change highlight' >
|
||||
<t t-esc='widget.format_currency_no_symbol(order.getChange(line))' />
|
||||
</td>
|
||||
</t>
|
||||
<t t-if='!order.getChange(line)'>
|
||||
<td class='col-change' ></td>
|
||||
</t>
|
||||
|
||||
<td class='col-name' > <t t-esc='line.name' /> </td>
|
||||
<td class='delete-button' t-att-data-cid='line.cid'> <i class='fa fa-times-circle' /> </td>
|
||||
</tr>
|
||||
</t>
|
||||
<t t-if='!line.selected'>
|
||||
<tr class='paymentline' t-att-data-cid='line.cid'>
|
||||
<td class='col-due'> <t t-esc='widget.format_currency_no_symbol(order.getDueLeft(line))' /> </td>
|
||||
<td class='col-tendered'> <t t-esc='widget.format_currency_no_symbol(line.get_amount())' /> </td>
|
||||
<td class='col-change'>
|
||||
<t t-if='order.getChange(line)'>
|
||||
<t t-esc='widget.format_currency_no_symbol(order.getChange(line))' />
|
||||
</t>
|
||||
</td>
|
||||
<td class='col-name'> <t t-esc='line.name' /> </td>
|
||||
<td class='delete-button' t-att-data-cid='line.cid'> <i class='fa fa-times-circle' /> </td>
|
||||
</tr>
|
||||
</t>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
</t>
|
||||
|
||||
</t>
|
||||
|
||||
<t t-name="PaymentScreen-Numpad">
|
||||
<div class="numpad">
|
||||
<button class="input-button number-char" data-action='1'>1</button>
|
||||
<button class="input-button number-char" data-action='2'>2</button>
|
||||
<button class="input-button number-char" data-action='3'>3</button>
|
||||
<button class="mode-button" data-action='+10'>+10</button>
|
||||
<br />
|
||||
<button class="input-button number-char" data-action='4'>4</button>
|
||||
<button class="input-button number-char" data-action='5'>5</button>
|
||||
<button class="input-button number-char" data-action='6'>6</button>
|
||||
<button class="mode-button" data-action='+20'>+20</button>
|
||||
<br />
|
||||
<button class="input-button number-char" data-action='7'>7</button>
|
||||
<button class="input-button number-char" data-action='8'>8</button>
|
||||
<button class="input-button number-char" data-action='9'>9</button>
|
||||
<button class="mode-button" data-action='+50'>+50</button>
|
||||
<br />
|
||||
<button class="input-button numpad-char" data-action='CLEAR' >C</button>
|
||||
<button class="input-button number-char" data-action='0'>0</button>
|
||||
<button class="input-button number-char" data-action='.'>.</button>
|
||||
<button class="input-button numpad-backspace" data-action='BACKSPACE' >
|
||||
<img src="/point_of_sale/static/src/img/backspace.png" width="24" height="21" />
|
||||
</button>
|
||||
<br />
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="PaymentScreen-Paymentmethods">
|
||||
<div class='paymentmethods'>
|
||||
<t t-foreach="widget.pos.cashregisters" t-as="cashregister">
|
||||
<div class="paymentmethod" t-att-data-id="cashregister.journal_id[0]">
|
||||
<t t-esc="cashregister.journal_id[1]" />
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="PaymentScreenWidget">
|
||||
<div class='payment-screen screen'>
|
||||
<div class='screen-content'>
|
||||
<div class='top-content'>
|
||||
<span class='button back'>
|
||||
<i class='fa fa-angle-double-left'></i>
|
||||
Back
|
||||
</span>
|
||||
<h1>Payment</h1>
|
||||
<span class='button next'>
|
||||
Validate
|
||||
<i class='fa fa-angle-double-right'></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class='left-content pc40 touch-scrollable scrollable-y'>
|
||||
|
||||
<div class='paymentmethods-container'>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class='right-content pc60 touch-scrollable scrollable-y'>
|
||||
|
||||
<section class='paymentlines-container'>
|
||||
</section>
|
||||
|
||||
<section class='payment-numpad'>
|
||||
</section>
|
||||
|
||||
<div class='payment-buttons'>
|
||||
<t t-if='widget.pos.config.iface_invoicing'>
|
||||
<div t-attf-class='button js_invoice #{ widget.pos.get_order().is_to_invoice() ? "highlight" : ""} '>
|
||||
<i class='fa fa-file-text-o' /> Invoice
|
||||
</div>
|
||||
</t>
|
||||
<t t-if='widget.pos.config.iface_cashdrawer'>
|
||||
<div class='button js_invoice'>
|
||||
<i class='fa fa-archive' /> Open Cashbox
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</t>
|
||||
|
||||
<t t-name="ReceiptScreenWidget">
|
||||
<div class="receipt-screen screen touch-scrollable" >
|
||||
<div class="pos-step-container">
|
||||
<div class="pos-receipt-container">
|
||||
<div class='receipt-screen screen'>
|
||||
<div class='screen-content'>
|
||||
<div class='top-content'>
|
||||
<h1>Receipt</h1>
|
||||
<span class='button next'>
|
||||
Next Order
|
||||
<i class='fa fa-angle-double-right'></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="centered-content">
|
||||
<div class="button print">
|
||||
<i class='fa fa-print'></i> Print
|
||||
</div>
|
||||
<div class="pos-receipt-container">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -586,23 +715,55 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="ErrorSessionPopupWidget">
|
||||
<t t-name="FullscreenPopupWidget">
|
||||
<div class="modal-dialog">
|
||||
<div class="popup popup-nosession">
|
||||
<p class="message">Sorry, we could not create a session for this user.</p>
|
||||
<div class="popup popup-fullscreen">
|
||||
<p class="message">Fullscreen Setup</p>
|
||||
|
||||
<t t-if='widget.ismobile()'>
|
||||
<p class="comment">
|
||||
The best way to make the point of sale fullscreen on mobile
|
||||
devices is to add the point of sale to your home screen. On
|
||||
iPhone and iPad this is done by tapping <img src='/point_of_sale/static/src/img/ios-share-icon.png' />
|
||||
and then <i>Add to Homescreen</i>
|
||||
</p>
|
||||
<p class='comment'>
|
||||
This also works on Android with the Chrome Beta Browser, using the <i>Add to Homescreen</i> option
|
||||
in the browser's menu.
|
||||
</p>
|
||||
<p class='comment'>
|
||||
If you want to work in fullscreen just this time tap the <i>Go Fullscreen</i> button.
|
||||
</p>
|
||||
</t>
|
||||
|
||||
<t t-if='!widget.ismobile()'>
|
||||
<p class="comment">
|
||||
The best way to make the point of sale fullscreen on desktop
|
||||
and laptops is to launch your browser in kiosk mode. Please
|
||||
refer to your browser's documentation for the specific
|
||||
instructions.
|
||||
</p>
|
||||
<p class="comment">
|
||||
If you want to work in fullscreen just this time, click the <i> Go Fullscreen</i> button.
|
||||
</p>
|
||||
</t>
|
||||
|
||||
<div class="footer">
|
||||
<div class="button fullscreen">
|
||||
Go Fullscreen
|
||||
</div>
|
||||
<div class="button cancel">
|
||||
Cancel
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="ErrorNoClientPopupWidget">
|
||||
<t t-name="ErrorSessionPopupWidget">
|
||||
<div class="modal-dialog">
|
||||
<div class="popup popup-noclient">
|
||||
<p class="message">An anonymous order cannot be invoiced</p>
|
||||
<div class="footer">
|
||||
<div class="button">
|
||||
Ok
|
||||
</div>
|
||||
</div>
|
||||
<div class="popup popup-nosession">
|
||||
<p class="message">Sorry, we could not create a session for this user.</p>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
@@ -635,6 +796,20 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="ErrorTracebackPopupWidget">
|
||||
<div class="modal-dialog">
|
||||
<div class="popup popup-error">
|
||||
<p class="message"><t t-esc=" widget.message || 'Error' " /></p>
|
||||
<p class="comment traceback"><t t-esc=" widget.comment || '' "/></p>
|
||||
<div class="footer">
|
||||
<div class="button">
|
||||
Ok
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="ErrorBarcodePopupWidget">
|
||||
<div class="modal-dialog">
|
||||
<div class="popup popup-barcode">
|
||||
@@ -672,6 +847,29 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="UnsentOrdersPopupWidget">
|
||||
<div class="modal-dialog">
|
||||
<div class="popup popup-unsent-orders">
|
||||
<p class="message">Unsent Orders</p>
|
||||
<t t-if='widget.pos.db.get_orders().length === 0'>
|
||||
<p class='comment'>
|
||||
There are no unsent orders
|
||||
</p>
|
||||
</t>
|
||||
<t t-if='widget.pos.db.get_orders().length > 0'>
|
||||
<p class='comment traceback'>
|
||||
<t t-esc='JSON.stringify(widget.pos.db.get_orders(),null,2)' />
|
||||
</p>
|
||||
</t>
|
||||
<div class="footer">
|
||||
<div class="button confirm">
|
||||
Ok
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="Product">
|
||||
<span class='product' t-att-data-product-id="product.id">
|
||||
<div class="product-img">
|
||||
@@ -779,6 +977,12 @@
|
||||
<li class="button reference">Reference</li>
|
||||
</ul>
|
||||
|
||||
<p class="category">Unsent Orders</p>
|
||||
<ul>
|
||||
<li class="button show_orders">Show All Unsent Orders</li>
|
||||
<li class="button delete_orders">Delete All Unsent Orders</li>
|
||||
</ul>
|
||||
|
||||
<p class="category">Hardware Status</p>
|
||||
<ul>
|
||||
<li class="status weighting">Weighting</li>
|
||||
@@ -818,12 +1022,6 @@
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
<t t-name="PaypadButtonWidget">
|
||||
<button class="paypad-button" t-att-cash-register-id="widget.cashregister.id">
|
||||
<t t-esc="widget.cashregister.journal.name"/>
|
||||
</button>
|
||||
</t>
|
||||
|
||||
<t t-name="OrderButtonWidget">
|
||||
<span class="order-button select-order">
|
||||
<t t-if='widget.selected'>
|
||||
@@ -944,30 +1142,6 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="ActionBarWidget">
|
||||
<div class="pos-actionbar">
|
||||
<ul class="pos-actionbar-button-list">
|
||||
</ul>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="ActionButtonWidget">
|
||||
<li t-att-class=" 'button '+ (widget.rightalign ? 'rightalign ' : '') + (widget.disabled ? 'disabled ' : '')">
|
||||
<div class='label'>
|
||||
<t t-esc="widget.label" />
|
||||
</div>
|
||||
</li>
|
||||
</t>
|
||||
|
||||
<t t-name="ActionButtonWidgetWithIcon">
|
||||
<li t-att-class=" 'button '+ (widget.rightalign ? 'rightalign ' : '') + (widget.disabled ? 'disabled ' : '')">
|
||||
<div class='icon'>
|
||||
<img t-att-src="widget.icon" />
|
||||
<div class='iconlabel'><t t-esc="widget.label" /></div>
|
||||
</div>
|
||||
</li>
|
||||
</t>
|
||||
|
||||
<!-- Onscreen Keyboard :
|
||||
http://net.tutsplus.com/tutorials/javascript-ajax/creating-a-keyboard-with-css-and-jquery/ -->
|
||||
<t t-name="OnscreenKeyboardFull">
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
import openerp.tests
|
||||
|
||||
@openerp.tests.common.at_install(False)
|
||||
@openerp.tests.common.post_install(True)
|
||||
class TestUi(openerp.tests.HttpCase):
|
||||
def test_01_pos_basic_order(self):
|
||||
self.phantom_js("/", "openerp.Tour.run('pos_basic_order', 'test')", "openerp.Tour.tours.pos_basic_order", login="admin")
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import discount
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# 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': 'Point of Sale Discounts',
|
||||
'version': '1.0',
|
||||
'category': 'Point of Sale',
|
||||
'sequence': 6,
|
||||
'summary': 'Simple Discounts in the Point of Sale ',
|
||||
'description': """
|
||||
|
||||
=======================
|
||||
|
||||
This module allows the cashier to quickly give a percentage
|
||||
sale discount to a customer.
|
||||
|
||||
""",
|
||||
'author': 'OpenERP SA',
|
||||
'depends': ['point_of_sale'],
|
||||
'data': [
|
||||
'views/views.xml',
|
||||
'views/templates.xml'
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
@@ -0,0 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import logging
|
||||
|
||||
import openerp
|
||||
|
||||
from openerp import tools
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
|
||||
class pos_config(osv.osv):
|
||||
_inherit = 'pos.config'
|
||||
_columns = {
|
||||
'discount_pc': fields.float('Discount Percentage', help='The discount percentage'),
|
||||
'discount_product_id': fields.many2one('product.product','Discount Product', help='The product used to model the discount'),
|
||||
}
|
||||
_defaults = {
|
||||
'discount_pc': 10,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
openerp.pos_discount = function(instance){
|
||||
var module = instance.point_of_sale;
|
||||
var round_pr = instance.web.round_precision
|
||||
var QWeb = instance.web.qweb;
|
||||
|
||||
QWeb.add_template('/pos_discount/static/src/xml/discount.xml');
|
||||
|
||||
module.PosWidget.include({
|
||||
build_widgets: function(){
|
||||
var self = this;
|
||||
this._super();
|
||||
|
||||
if(!this.pos.config.discount_product_id){
|
||||
return;
|
||||
}
|
||||
|
||||
var discount = $(QWeb.render('DiscountButton'));
|
||||
|
||||
discount.click(function(){
|
||||
var order = self.pos.get('selectedOrder');
|
||||
var product = self.pos.db.get_product_by_id(self.pos.config.discount_product_id[0]);
|
||||
var discount = - self.pos.config.discount_pc/ 100.0 * order.getTotalTaxIncluded();
|
||||
if( discount < 0 ){
|
||||
order.addProduct(product, { price: discount });
|
||||
}
|
||||
});
|
||||
|
||||
discount.appendTo(this.$('.control-buttons'));
|
||||
this.$('.control-buttons').removeClass('oe_hidden');
|
||||
},
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="DiscountButton">
|
||||
<div class='control-button js_discount'>
|
||||
<i class='fa fa-tag' /> Discount
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<template id="assets_frontend" inherit_id="web.assets_common">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/pos_discount/static/src/js/discount.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record model="ir.ui.view" id="view_pos_config_form">
|
||||
<field name="name">pos.config.form.view</field>
|
||||
<field name="model">pos.config</field>
|
||||
<field name="inherit_id" ref="point_of_sale.view_pos_config_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group[@string='Receipt']" position="after">
|
||||
<group string="Discounts" col="4" >
|
||||
<field name='discount_pc' />
|
||||
<field name="discount_product_id" />
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import restaurant
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# 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': 'Restaurant',
|
||||
'version': '1.0',
|
||||
'category': 'Point of Sale',
|
||||
'sequence': 6,
|
||||
'summary': 'Restaurant extensions for the Point of Sale ',
|
||||
'description': """
|
||||
|
||||
=======================
|
||||
|
||||
This module adds several restaurant features to the Point of Sale:
|
||||
- Bill Printing: Allows you to print a receipt before the order is paid
|
||||
- Bill Splitting: Allows you to split an order into different orders
|
||||
- Kitchen Order Printing: allows you to print orders updates to kitchen or bar printers
|
||||
|
||||
""",
|
||||
'author': 'OpenERP SA',
|
||||
'depends': ['point_of_sale'],
|
||||
'data': [
|
||||
'restaurant_view.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/templates.xml',
|
||||
],
|
||||
'qweb':[
|
||||
'static/src/xml/multiprint.xml',
|
||||
'static/src/xml/splitbill.xml',
|
||||
'static/src/xml/printbill.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
@@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import logging
|
||||
|
||||
import openerp
|
||||
from openerp import tools
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class restaurant_printer(osv.osv):
|
||||
_name = 'restaurant.printer'
|
||||
|
||||
_columns = {
|
||||
'name' : fields.char('Printer Name', size=32, required=True, help='An internal identification of the printer'),
|
||||
'proxy_ip': fields.char('Proxy IP Address', size=32, help="The IP Address or hostname of the Printer's hardware proxy"),
|
||||
'product_categories_ids': fields.many2many('pos.category','printer_category_rel', 'printer_id','category_id',string='Printed Product Categories'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'name' : 'Printer',
|
||||
}
|
||||
|
||||
class pos_config(osv.osv):
|
||||
_inherit = 'pos.config'
|
||||
_columns = {
|
||||
'iface_splitbill': fields.boolean('Bill Splitting', help='Enables Bill Splitting in the Point of Sale'),
|
||||
'iface_printbill': fields.boolean('Bill Printing', help='Allows to print the Bill before payment'),
|
||||
'printer_ids': fields.many2many('restaurant.printer','pos_config_printer_rel', 'config_id','printer_id',string='Order Printers'),
|
||||
}
|
||||
_defaults = {
|
||||
'iface_splitbill': False,
|
||||
'iface_printbill': False,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="view_restaurant_printer_form">
|
||||
<field name="name">Order Printer</field>
|
||||
<field name="model">restaurant.printer</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="POS Printer" version="7.0">
|
||||
<group col="2">
|
||||
<field name="name" />
|
||||
<field name="proxy_ip" />
|
||||
<field name="product_categories_ids" />
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_restaurant_printer_form">
|
||||
<field name="name">Order Printers</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">restaurant.printer</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to add a Restaurant Order Printer.
|
||||
</p><p>
|
||||
Order Printers are used by restaurants and bars to print the
|
||||
order updates in the kitchen/bar when the waiter updates the order.
|
||||
</p><p>
|
||||
Each Order Printer has an IP Address that defines the PosBox/Hardware
|
||||
Proxy where the printer can be found, and a list of product categories.
|
||||
An Order Printer will only print updates for prodcuts belonging to one of
|
||||
its categories.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_restaurant_printer">
|
||||
<field name="name">Order Printers</field>
|
||||
<field name="model">restaurant.printer</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Restaurant Order Printers">
|
||||
<field name="name" />
|
||||
<field name="proxy_ip" />
|
||||
<field name="product_categories_ids" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
parent="point_of_sale.menu_point_config_product"
|
||||
action="action_restaurant_printer_form"
|
||||
id="menu_restaurant_printer_all"
|
||||
sequence="30"
|
||||
groups="point_of_sale.group_pos_manager"/>
|
||||
|
||||
<record model="ir.ui.view" id="view_pos_config_form">
|
||||
<field name="name">pos.config.form.view.inherit</field>
|
||||
<field name="model">pos.config</field>
|
||||
<field name="inherit_id" ref="point_of_sale.view_pos_config_form"></field>
|
||||
<field name="arch" type="xml">
|
||||
<sheet position='inside'>
|
||||
<group string="Bar & Restaurant" >
|
||||
<field name="iface_splitbill" />
|
||||
<field name="iface_printbill" />
|
||||
<field name="printer_ids" />
|
||||
</group>
|
||||
</sheet>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -0,0 +1,3 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_restaurant_printer,restaurant.printer.user,model_restaurant_printer,point_of_sale.group_pos_user,1,0,0,0
|
||||
access_restaurant_printer_manager,restaurant.printer.manager,model_restaurant_printer,point_of_sale.group_pos_manager,1,0,0,0
|
||||
|
@@ -0,0 +1,2 @@
|
||||
/* --- Restaurant Specific CSS --- */
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
openerp.restaurant = function(instance){
|
||||
|
||||
var module = instance.point_of_sale;
|
||||
|
||||
openerp_restaurant_multiprint(instance,module);
|
||||
|
||||
openerp_restaurant_splitbill(instance,module);
|
||||
|
||||
openerp_restaurant_printbill(instance,module);
|
||||
|
||||
};
|
||||
@@ -0,0 +1,216 @@
|
||||
function openerp_restaurant_multiprint(instance,module){
|
||||
var QWeb = instance.web.qweb;
|
||||
var _t = instance.web._t;
|
||||
|
||||
module.Printer = instance.web.Class.extend(openerp.PropertiesMixin,{
|
||||
init: function(parent,options){
|
||||
openerp.PropertiesMixin.init.call(this,parent);
|
||||
var self = this;
|
||||
options = options || {};
|
||||
var url = options.url || 'http://localhost:8069';
|
||||
this.connection = new instance.web.Session(undefined,url, { use_cors: true});
|
||||
this.host = url;
|
||||
this.receipt_queue = [];
|
||||
},
|
||||
print: function(receipt){
|
||||
var self = this;
|
||||
if(receipt){
|
||||
this.receipt_queue.push(receipt);
|
||||
}
|
||||
var aborted = false;
|
||||
function send_printing_job(){
|
||||
if(self.receipt_queue.length > 0){
|
||||
var r = self.receipt_queue.shift();
|
||||
self.connection.rpc('/hw_proxy/print_xml_receipt',{receipt: r},{timeout: 5000})
|
||||
.then(function(){
|
||||
send_printing_job();
|
||||
},function(){
|
||||
self.receipt_queue.unshift(r);
|
||||
});
|
||||
}
|
||||
}
|
||||
send_printing_job();
|
||||
},
|
||||
});
|
||||
|
||||
module.PosModel.prototype.models.push({
|
||||
model: 'restaurant.printer',
|
||||
fields: ['name','proxy_ip','product_categories_ids'],
|
||||
domain: null,
|
||||
loaded: function(self,printers){
|
||||
var active_printers = {};
|
||||
for (var i = 0; i < self.config.printer_ids.length; i++) {
|
||||
active_printers[self.config.printer_ids[i]] = true;
|
||||
}
|
||||
|
||||
self.printers = [];
|
||||
for(var i = 0; i < printers.length; i++){
|
||||
if(active_printers[printers[i].id]){
|
||||
var printer = new module.Printer(self,{url:'http://'+printers[i].proxy_ip+':8069'});
|
||||
printer.config = printers[i];
|
||||
self.printers.push(printer);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
module.Order = module.Order.extend({
|
||||
lineResume: function(){
|
||||
var resume = {};
|
||||
this.get('orderLines').each(function(item){
|
||||
var line = item.export_as_JSON();
|
||||
if( typeof resume[line.product_id] === 'undefined'){
|
||||
resume[line.product_id] = line.qty;
|
||||
}else{
|
||||
resume[line.product_id] += line.qty;
|
||||
}
|
||||
});
|
||||
return resume;
|
||||
},
|
||||
saveChanges: function(){
|
||||
this.old_resume = this.lineResume();
|
||||
},
|
||||
computeChanges: function(categories){
|
||||
var current = this.lineResume();
|
||||
var old = this.old_resume || {};
|
||||
var json = this.export_as_JSON();
|
||||
var add = [];
|
||||
var rem = [];
|
||||
|
||||
for( product in current){
|
||||
if (typeof old[product] === 'undefined'){
|
||||
add.push({
|
||||
'id': product,
|
||||
'name': this.pos.db.get_product_by_id(product).name,
|
||||
'quantity': current[product],
|
||||
});
|
||||
}else if( old[product] < current[product]){
|
||||
add.push({
|
||||
'id': product,
|
||||
'name': this.pos.db.get_product_by_id(product).name,
|
||||
'quantity': current[product] - old[product],
|
||||
});
|
||||
}else if( old[product] > current[product]){
|
||||
rem.push({
|
||||
'id': product,
|
||||
'name': this.pos.db.get_product_by_id(product).name,
|
||||
'quantity': old[product] - current[product],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for( product in old){
|
||||
if(typeof current[product] === 'undefined'){
|
||||
rem.push({
|
||||
'id': product,
|
||||
'name': this.pos.db.get_product_by_id(product).name,
|
||||
'quantity': old[product],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(categories && categories.length > 0){
|
||||
// filter the added and removed orders to only contains
|
||||
// products that belong to one of the categories supplied as a parameter
|
||||
|
||||
var self = this;
|
||||
function product_in_category(product_id){
|
||||
var cat = self.pos.db.get_product_by_id(product_id).pos_categ_id[0];
|
||||
while(cat){
|
||||
for(var i = 0; i < categories.length; i++){
|
||||
if(cat === categories[i]){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
cat = self.pos.db.get_category_parent_id(cat);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var _add = [];
|
||||
var _rem = [];
|
||||
|
||||
for(var i = 0; i < add.length; i++){
|
||||
if(product_in_category(add[i].id)){
|
||||
_add.push(add[i]);
|
||||
}
|
||||
}
|
||||
add = _add;
|
||||
|
||||
for(var i = 0; i < rem.length; i++){
|
||||
if(product_in_category(rem[i].id)){
|
||||
_rem.push(rem[i]);
|
||||
}
|
||||
}
|
||||
rem = _rem;
|
||||
}
|
||||
|
||||
return {
|
||||
'new': add,
|
||||
'cancelled': rem,
|
||||
'table': json.table || 'unknown table',
|
||||
'name': json.name || 'unknown order',
|
||||
'sequence_number': json.sequence_number || 0,
|
||||
};
|
||||
|
||||
},
|
||||
printChanges: function(){
|
||||
var printers = this.pos.printers;
|
||||
for(var i = 0; i < printers.length; i++){
|
||||
var changes = this.computeChanges(printers[i].config.product_categories_ids);
|
||||
if ( changes['new'].length > 0 || changes['cancelled'].length > 0){
|
||||
var receipt = QWeb.render('OrderChangeReceipt',{changes:changes, widget:this});
|
||||
printers[i].print(receipt);
|
||||
}
|
||||
}
|
||||
},
|
||||
hasChangesToPrint: function(){
|
||||
var printers = this.pos.printers;
|
||||
for(var i = 0; i < printers.length; i++){
|
||||
var changes = this.computeChanges(printers[i].config.product_categories_ids);
|
||||
if ( changes['new'].length > 0 || changes['cancelled'].length > 0){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
module.PosWidget.include({
|
||||
build_widgets: function(){
|
||||
var self = this;
|
||||
this._super();
|
||||
|
||||
if(this.pos.printers.length){
|
||||
var submitorder = $(QWeb.render('SubmitOrderButton'));
|
||||
|
||||
submitorder.click(function(){
|
||||
var order = self.pos.get('selectedOrder');
|
||||
if(order.hasChangesToPrint()){
|
||||
order.printChanges();
|
||||
order.saveChanges();
|
||||
self.pos_widget.order_widget.update_summary();
|
||||
}
|
||||
});
|
||||
|
||||
submitorder.appendTo(this.$('.control-buttons'));
|
||||
this.$('.control-buttons').removeClass('oe_hidden');
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
module.OrderWidget.include({
|
||||
update_summary: function(){
|
||||
this._super();
|
||||
var order = this.pos.get('selectedOrder');
|
||||
|
||||
if(order.hasChangesToPrint()){
|
||||
this.pos_widget.$('.order-submit').addClass('highlight');
|
||||
}else{
|
||||
this.pos_widget.$('.order-submit').removeClass('highlight');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
function openerp_restaurant_printbill(instance,module){
|
||||
var QWeb = instance.web.qweb;
|
||||
var _t = instance.web._t;
|
||||
|
||||
module.PosWidget.include({
|
||||
build_widgets: function(){
|
||||
var self = this;
|
||||
this._super();
|
||||
|
||||
if(this.pos.config.iface_printbill){
|
||||
var printbill = $(QWeb.render('PrintBillButton'));
|
||||
|
||||
printbill.click(function(){
|
||||
var order = self.pos.get('selectedOrder');
|
||||
if(order.get('orderLines').models.length > 0){
|
||||
var receipt = order.export_for_printing();
|
||||
self.pos.proxy.print_receipt(QWeb.render('BillReceipt',{
|
||||
receipt: receipt, widget: self,
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
printbill.appendTo(this.$('.control-buttons'));
|
||||
this.$('.control-buttons').removeClass('oe_hidden');
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
function openerp_restaurant_splitbill(instance, module){
|
||||
var QWeb = instance.web.qweb;
|
||||
var _t = instance.web._t;
|
||||
|
||||
module.SplitbillScreenWidget = module.ScreenWidget.extend({
|
||||
template: 'SplitbillScreenWidget',
|
||||
|
||||
show_leftpane: false,
|
||||
previous_screen: 'products',
|
||||
|
||||
renderElement: function(){
|
||||
var self = this;
|
||||
this._super();
|
||||
var order = this.pos.get('selectedOrder');
|
||||
if(!order){
|
||||
return;
|
||||
}
|
||||
var orderlines = order.get('orderLines').models;
|
||||
for(var i = 0; i < orderlines.length; i++){
|
||||
var line = orderlines[i];
|
||||
linewidget = $(QWeb.render('SplitOrderline',{
|
||||
widget:this,
|
||||
line:line,
|
||||
selected: false,
|
||||
quantity: 0,
|
||||
id: line.id,
|
||||
}));
|
||||
linewidget.data('id',line.id);
|
||||
this.$('.orderlines').append(linewidget);
|
||||
}
|
||||
this.$('.back').click(function(){
|
||||
self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
|
||||
});
|
||||
},
|
||||
|
||||
lineselect: function($el,order,neworder,splitlines,line_id){
|
||||
var split = splitlines[line_id] || {'quantity': 0, line: null};
|
||||
var line = order.getOrderline(line_id);
|
||||
|
||||
if( !line.get_unit().groupable ){
|
||||
if( split.quantity !== line.get_quantity()){
|
||||
split.quantity = line.get_quantity();
|
||||
}else{
|
||||
split.quantity = 0;
|
||||
}
|
||||
}else{
|
||||
if( split.quantity < line.get_quantity()){
|
||||
split.quantity += line.get_unit().rounding;
|
||||
if(split.quantity > line.get_quantity()){
|
||||
split.quantity = line.get_quantity();
|
||||
}
|
||||
}else{
|
||||
split.quantity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if( split.quantity ){
|
||||
if ( !split.line ){
|
||||
split.line = line.clone();
|
||||
neworder.addOrderline(split.line);
|
||||
}
|
||||
split.line.set_quantity(split.quantity);
|
||||
}else if( split.line ) {
|
||||
neworder.removeOrderline(split.line);
|
||||
split.line = null;
|
||||
}
|
||||
|
||||
splitlines[line_id] = split;
|
||||
$el.replaceWith($(QWeb.render('SplitOrderline',{
|
||||
widget: this,
|
||||
line: line,
|
||||
selected: split.quantity !== 0,
|
||||
quantity: split.quantity,
|
||||
id: line_id,
|
||||
})));
|
||||
this.$('.order-info .subtotal').text(this.format_currency(neworder.getSubtotal()));
|
||||
},
|
||||
|
||||
pay: function($el,order,neworder,splitlines,cashregister_id){
|
||||
var orderlines = order.get('orderLines').models;
|
||||
var empty = true;
|
||||
var full = true;
|
||||
|
||||
for(var i = 0; i < orderlines.length; i++){
|
||||
var id = orderlines[i].id;
|
||||
var split = splitlines[id];
|
||||
if(!split){
|
||||
full = false;
|
||||
}else{
|
||||
if(split.quantity){
|
||||
empty = false;
|
||||
if(split.quantity !== orderlines[i].get_quantity()){
|
||||
full = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(empty){
|
||||
return;
|
||||
}
|
||||
|
||||
for(var i = 0; i < this.pos.cashregisters.length; i++){
|
||||
if(this.pos.cashregisters[i].id === cashregister_id){
|
||||
var cashregister = this.pos.cashregisters[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(full){
|
||||
order.addPaymentline(cashregister);
|
||||
this.pos_widget.screen_selector.set_current_screen('payment');
|
||||
}else{
|
||||
for(var id in splitlines){
|
||||
var split = splitlines[id];
|
||||
var line = order.getOrderline(parseInt(id));
|
||||
line.set_quantity(line.get_quantity() - split.quantity);
|
||||
if(Math.abs(line.get_quantity()) < 0.00001){
|
||||
order.removeOrderline(line);
|
||||
}
|
||||
delete splitlines[id];
|
||||
}
|
||||
neworder.addPaymentline(cashregister);
|
||||
neworder.set_screen_data('screen','payment');
|
||||
|
||||
// for the kitchen printer we assume that everything
|
||||
// has already been sent to the kitchen before splitting
|
||||
// the bill. So we save all changes both for the old
|
||||
// order and for the new one. This is not entirely correct
|
||||
// but avoids flooding the kitchen with unnecessary orders.
|
||||
// Not sure what to do in this case.
|
||||
|
||||
if ( neworder.saveChanges ) {
|
||||
order.saveChanges();
|
||||
neworder.saveChanges();
|
||||
}
|
||||
|
||||
this.pos.get('orders').add(neworder);
|
||||
this.pos.set('selectedOrder',neworder);
|
||||
}
|
||||
},
|
||||
show: function(){
|
||||
var self = this;
|
||||
this._super();
|
||||
this.renderElement();
|
||||
|
||||
var order = this.pos.get('selectedOrder');
|
||||
var neworder = new module.Order({
|
||||
pos: this.pos,
|
||||
temporary: true,
|
||||
});
|
||||
neworder.set('client',order.get('client'));
|
||||
|
||||
var splitlines = {};
|
||||
|
||||
this.$('.orderlines').on('click','.orderline',function(){
|
||||
var id = parseInt($(this).data('id'));
|
||||
var $el = $(this);
|
||||
self.lineselect($el,order,neworder,splitlines,id);
|
||||
});
|
||||
|
||||
this.$('.paymentmethod').click(function(){
|
||||
var id = parseInt($(this).data('id'));
|
||||
var $el = $(this);
|
||||
self.pay($el,order,neworder,splitlines,id);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
module.PosWidget.include({
|
||||
build_widgets: function(){
|
||||
var self = this;
|
||||
this._super();
|
||||
|
||||
if(this.pos.config.iface_splitbill){
|
||||
this.splitbill_screen = new module.SplitbillScreenWidget(this,{});
|
||||
this.splitbill_screen.appendTo(this.$('.screens'));
|
||||
this.screen_selector.add_screen('splitbill',this.splitbill_screen);
|
||||
|
||||
var splitbill = $(QWeb.render('SplitbillButton'));
|
||||
|
||||
splitbill.click(function(){
|
||||
if(self.pos.get('selectedOrder').get('orderLines').models.length > 0){
|
||||
self.pos_widget.screen_selector.set_current_screen('splitbill');
|
||||
}
|
||||
});
|
||||
|
||||
splitbill.appendTo(this.$('.control-buttons'));
|
||||
this.$('.control-buttons').removeClass('oe_hidden');
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="SubmitOrderButton">
|
||||
<span class="control-button order-submit">
|
||||
<i class="fa fa-cutlery"></i>
|
||||
Order
|
||||
</span>
|
||||
</t>
|
||||
|
||||
<t t-name="OrderChangeReceipt">
|
||||
<receipt
|
||||
align='center'
|
||||
width='40'
|
||||
size='double-height'
|
||||
line-ratio='0.6'
|
||||
value-decimals='3'
|
||||
value-thousands-separator=''
|
||||
value-autoint='on'
|
||||
>
|
||||
<div>#<t t-esc="changes.sequence_number" /></div>
|
||||
<div size='normal'><t t-esc="changes.name" /></div>
|
||||
<br />
|
||||
<br />
|
||||
<t t-if="changes.cancelled.length > 0">
|
||||
<div color='red'>
|
||||
<div bold='on' size='double'>CANCELLED</div>
|
||||
<br />
|
||||
<br />
|
||||
<t t-foreach="changes.cancelled" t-as="change">
|
||||
<line>
|
||||
<left><t t-esc="change.name" /></left>
|
||||
<right><value><t t-esc="change.quantity" /></value></right>
|
||||
</line>
|
||||
</t>
|
||||
<br />
|
||||
<br />
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="changes.new.length > 0">
|
||||
<div bold='on' size='double'>NEW</div>
|
||||
<br />
|
||||
<br />
|
||||
<t t-foreach="changes.new" t-as="change">
|
||||
<line>
|
||||
<left><t t-esc="change.name" /></left>
|
||||
<right><value><t t-esc="change.quantity" /></value></right>
|
||||
</line>
|
||||
</t>
|
||||
<br />
|
||||
<br />
|
||||
</t>
|
||||
</receipt>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,143 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="PrintBillButton">
|
||||
<span class="control-button order-printbill">
|
||||
<i class="fa fa-print"></i>
|
||||
Bill
|
||||
</span>
|
||||
</t>
|
||||
|
||||
<t t-name="BillReceipt">
|
||||
<receipt align='center' width='40' value-thousands-separator='' >
|
||||
<t t-if='receipt.company.logo'>
|
||||
<img t-att-src='receipt.company.logo' />
|
||||
<br/>
|
||||
</t>
|
||||
<t t-if='!receipt.company.logo'>
|
||||
<h1><t t-esc='receipt.company.name' /></h1>
|
||||
<br/>
|
||||
</t>
|
||||
<div font='b'>
|
||||
<t t-if='receipt.shop.name'>
|
||||
<div><t t-esc='receipt.shop.name' /></div>
|
||||
</t>
|
||||
<t t-if='receipt.company.contact_address'>
|
||||
<div><t t-esc='receipt.company.contact_address' /></div>
|
||||
</t>
|
||||
<t t-if='receipt.company.phone'>
|
||||
<div>Tel:<t t-esc='receipt.company.phone' /></div>
|
||||
</t>
|
||||
<t t-if='receipt.company.vat'>
|
||||
<div>VAT:<t t-esc='receipt.company.vat' /></div>
|
||||
</t>
|
||||
<t t-if='receipt.company.email'>
|
||||
<div><t t-esc='receipt.company.email' /></div>
|
||||
</t>
|
||||
<t t-if='receipt.company.website'>
|
||||
<div><t t-esc='receipt.company.website' /></div>
|
||||
</t>
|
||||
<t t-if='receipt.header'>
|
||||
<div><t t-esc='receipt.header' /></div>
|
||||
</t>
|
||||
<t t-if='receipt.cashier'>
|
||||
<div>--------------------------------</div>
|
||||
<div>Served by <t t-esc='receipt.cashier' /></div>
|
||||
</t>
|
||||
</div>
|
||||
<br /><br />
|
||||
|
||||
<!-- Orderlines -->
|
||||
|
||||
<div line-ratio='0.6'>
|
||||
<t t-foreach='receipt.orderlines' t-as='line'>
|
||||
<t t-set='simple' t-value='line.discount === 0 and line.unit_name === "Unit(s)" and line.quantity === 1' />
|
||||
<t t-if='simple'>
|
||||
<line>
|
||||
<left><t t-esc='line.product_name' /></left>
|
||||
<right><value><t t-esc='line.price_display' /></value></right>
|
||||
</line>
|
||||
</t>
|
||||
<t t-if='!simple'>
|
||||
<line><left><t t-esc='line.product_name' /></left></line>
|
||||
<t t-if='line.discount !== 0'>
|
||||
<line indent='1'><left>Discount: <t t-esc='line.discount' />%</left></line>
|
||||
</t>
|
||||
<line indent='1'>
|
||||
<left>
|
||||
<value value-decimals='3' value-autoint='on'>
|
||||
<t t-esc='line.quantity' />
|
||||
</value>
|
||||
<t t-if='line.unit_name !== "Unit(s)"'>
|
||||
<t t-esc='line.unit_name' />
|
||||
</t>
|
||||
x
|
||||
<value value-decimals='2'>
|
||||
<t t-esc='line.price' />
|
||||
</value>
|
||||
</left>
|
||||
<right>
|
||||
<value><t t-esc='line.price_display' /></value>
|
||||
</right>
|
||||
</line>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- Subtotal -->
|
||||
<t t-set='taxincluded' t-value='Math.abs(receipt.subtotal - receipt.total_with_tax) <= 0.000001' />
|
||||
<t t-if='!taxincluded'>
|
||||
<line><right>--------</right></line>
|
||||
<line><left>Subtotal</left><right> <value><t t-esc="receipt.subtotal" /></value></right></line>
|
||||
<t t-foreach='receipt.tax_details' t-as='tax'>
|
||||
<line>
|
||||
<left><t t-esc='tax.name' /></left>
|
||||
<right><value><t t-esc='tax.amount' /></value></right>
|
||||
</line>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<!-- Total -->
|
||||
|
||||
<line><right>--------</right></line>
|
||||
<line size='double-height'>
|
||||
<left><pre> TOTAL</pre></left>
|
||||
<right><value><t t-esc='receipt.total_with_tax' /></value></right>
|
||||
</line>
|
||||
<br/><br/>
|
||||
|
||||
<!-- Extra Payment Info -->
|
||||
|
||||
<t t-if='receipt.total_discount'>
|
||||
<line>
|
||||
<left>Discounts</left>
|
||||
<right><value><t t-esc='receipt.total_discount'/></value></right>
|
||||
</line>
|
||||
</t>
|
||||
<t t-if='taxincluded'>
|
||||
<t t-foreach='receipt.tax_details' t-as='tax'>
|
||||
<line>
|
||||
<left><t t-esc='tax.name' /></left>
|
||||
<right><value><t t-esc='tax.amount' /></value></right>
|
||||
</line>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<!-- Footer -->
|
||||
<t t-if='receipt.footer'>
|
||||
<br/>
|
||||
<pre><t t-esc='receipt.footer' /></pre>
|
||||
<br/>
|
||||
<br/>
|
||||
</t>
|
||||
|
||||
<br/>
|
||||
<div font='b'>
|
||||
<div><t t-esc='receipt.name' /></div>
|
||||
<div><t t-esc='receipt.date.localestring' /></div>
|
||||
</div>
|
||||
|
||||
</receipt>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="SplitbillButton">
|
||||
<span class="control-button order-split">
|
||||
<i class="fa fa-copy"></i>
|
||||
Split
|
||||
</span>
|
||||
</t>
|
||||
|
||||
<t t-name="SplitOrderline">
|
||||
|
||||
<li t-attf-class="orderline #{ selected ? 'selected' : ''} #{ quantity !== line.get_quantity() ? 'partially' : '' }"
|
||||
t-att-data-id="id">
|
||||
<span class="product-name">
|
||||
<t t-esc="line.get_product().name"/>
|
||||
</span>
|
||||
<span class="price">
|
||||
<t t-esc="widget.format_currency(line.get_display_price())"/>
|
||||
</span>
|
||||
<ul class="info-list">
|
||||
<t t-if="line.get_quantity_str() !== '1'">
|
||||
<li class="info">
|
||||
<t t-if='selected and line.get_unit().groupable'>
|
||||
<em class='big'>
|
||||
<t t-esc='quantity' />
|
||||
</em>
|
||||
/
|
||||
<t t-esc="line.get_quantity_str()" />
|
||||
</t>
|
||||
<t t-if='!(selected and line.get_unit().groupable)'>
|
||||
<em>
|
||||
<t t-esc="line.get_quantity_str()" />
|
||||
</em>
|
||||
</t>
|
||||
<t t-esc="line.get_unit().name" />
|
||||
at
|
||||
<t t-esc="widget.format_currency(line.get_unit_price())" />
|
||||
/
|
||||
<t t-esc="line.get_unit().name" />
|
||||
</li>
|
||||
</t>
|
||||
<t t-if="line.get_discount_str() !== '0'">
|
||||
<li class="info">
|
||||
With a
|
||||
<em>
|
||||
<t t-esc="line.get_discount_str()" />%
|
||||
</em>
|
||||
discount
|
||||
</li>
|
||||
</t>
|
||||
</ul>
|
||||
</li>
|
||||
</t>
|
||||
|
||||
<t t-name="SplitbillScreenWidget">
|
||||
<div class='splitbill-screen screen'>
|
||||
<div class='screen-content'>
|
||||
<div class='top-content'>
|
||||
<span class='button back'>
|
||||
<i class='fa fa-angle-double-left'></i>
|
||||
Back
|
||||
</span>
|
||||
<h1>Bill Splitting</h1>
|
||||
</div>
|
||||
<div class='left-content touch-scrollable scrollable-y'>
|
||||
<div class='order'>
|
||||
<ul class='orderlines'>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class='right-content touch-scrollable scrollable-y'>
|
||||
<div class='order-info'>
|
||||
<span class='subtotal'><t t-esc='widget.format_currency(0.0)'/></span>
|
||||
</div>
|
||||
<div class='paymentmethods'>
|
||||
<t t-foreach="widget.pos.cashregisters" t-as="cashregister">
|
||||
<div class='button paymentmethod' t-att-data-id="cashregister.id">
|
||||
<t t-esc='cashregister.journal.name' />
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- vim:fdn=3:
|
||||
-->
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<template id="index" inherit_id='point_of_sale.index' name="Restaurant Index"><!DOCTYPE html>
|
||||
<xpath expr="//link[@id='pos-stylesheet']" position="after">
|
||||
<link rel="stylesheet" href="/restaurant/static/src/css/restaurant.css" />
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="assets_frontend" inherit_id="web.assets_common">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/restaurant/static/src/js/multiprint.js"></script>
|
||||
<script type="text/javascript" src="/restaurant/static/src/js/splitbill.js"></script>
|
||||
<script type="text/javascript" src="/restaurant/static/src/js/printbill.js"></script>
|
||||
<script type="text/javascript" src="/restaurant/static/src/js/main.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
Reference in New Issue
Block a user