[FIX] point_of_sale: restored removed report
The sale of the day report was removed during new api migration as it was thought, it could be replaced by pivot view. Due to the lack of some options and the problems to be printed, create similar report. This commit adds a report in the PoS interface to be printed on ESC/POS printers and add a JS wizard to restore it in the backend with date selection. The wizard is made in a new web widget because adding a wizard requires changes not compatible with stable version. Creating a proper wizard should be done in v10 opw-681833
This commit is contained in:
@@ -59,6 +59,7 @@ Main Features
|
||||
'views/report_statement.xml',
|
||||
'views/report_usersproduct.xml',
|
||||
'views/report_userlabel.xml',
|
||||
'views/report_saledetails.xml',
|
||||
'views/point_of_sale.xml',
|
||||
'views/point_of_sale_dashboard.xml',
|
||||
],
|
||||
|
||||
@@ -18,3 +18,10 @@ class PosController(http.Controller):
|
||||
return werkzeug.utils.redirect('/web#action=point_of_sale.action_client_pos_menu')
|
||||
pos_sessions.login()
|
||||
return request.render('point_of_sale.index')
|
||||
|
||||
@http.route('/pos/sale_details_report', type='http', auth='user')
|
||||
def print_sale_details(self, date_start=False, date_stop=False, **kw):
|
||||
r = request.env['report.point_of_sale.report_saledetails']
|
||||
pdf = request.env['report'].with_context(date_start=date_start, date_stop=date_stop).get_pdf(r, 'point_of_sale.report_saledetails')
|
||||
pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))]
|
||||
return request.make_response(pdf, headers=pdfhttpheaders)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from functools import partial
|
||||
|
||||
import psycopg2
|
||||
@@ -8,7 +9,7 @@ import psycopg2
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.tools import float_is_zero
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
from odoo.http import request
|
||||
import odoo.addons.decimal_precision as dp
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -733,3 +734,104 @@ class PosOrderLine(models.Model):
|
||||
def _get_tax_ids_after_fiscal_position(self):
|
||||
for line in self:
|
||||
line.tax_ids_after_fiscal_position = line.order_id.fiscal_position_id.map_tax(line.tax_ids)
|
||||
|
||||
|
||||
class ReportSaleDetails(models.AbstractModel):
|
||||
|
||||
_name = 'report.point_of_sale.report_saledetails'
|
||||
|
||||
|
||||
@api.model
|
||||
def get_sale_details(self, date_start=False, date_stop=False, company=False):
|
||||
""" Serialise the orders of the day information
|
||||
|
||||
params: date_start, date_stop string representing the day of order
|
||||
"""
|
||||
if not company:
|
||||
company = self.env.user.company_id
|
||||
|
||||
today = fields.Datetime.from_string(fields.Date.context_today(self))
|
||||
if date_start:
|
||||
date_start = fields.Datetime.from_string(date_start)
|
||||
else:
|
||||
# start by default today 00:00:00
|
||||
date_start = today
|
||||
|
||||
if date_stop:
|
||||
# set time to 23:59:59
|
||||
date_stop = fields.Datetime.from_string(date_stop) + timedelta(days=1, seconds=-1)
|
||||
else:
|
||||
# stop by default today 23:59:59
|
||||
date_stop = today + timedelta(days=1, seconds=-1)
|
||||
|
||||
# avoid a date_stop smaller than date_start
|
||||
date_stop = max(date_stop, date_start)
|
||||
|
||||
date_start = fields.Datetime.to_string(date_start)
|
||||
date_stop = fields.Datetime.to_string(date_stop)
|
||||
orders = self.env['pos.order'].search([('date_order', '>=', date_start), ('date_order', '<=', date_stop),
|
||||
('state', 'in', ['paid','invoiced','done']),
|
||||
('company_id', '=', company.id)])
|
||||
user_currency = self.env.user.company_id.currency_id
|
||||
|
||||
total = 0.0
|
||||
products_sold = {}
|
||||
taxes = {}
|
||||
for order in orders:
|
||||
if user_currency != order.pricelist_id.currency_id:
|
||||
total += order.pricelist_id.currency_id.compute(order.amount_total, user_currency)
|
||||
else:
|
||||
total += order.amount_total
|
||||
currency = order.session_id.currency_id
|
||||
|
||||
for line in order.lines:
|
||||
key = (line.product_id, line.price_unit, line.discount)
|
||||
products_sold.setdefault(key, 0.0)
|
||||
products_sold[key] += line.qty
|
||||
|
||||
if line.tax_ids_after_fiscal_position:
|
||||
line_taxes = line.tax_ids_after_fiscal_position.compute_all(line.price_unit * (1-(line.discount or 0.0)/100.0), currency, line.qty, product=line.product_id, partner=line.order_id.partner_id or False)
|
||||
for tax in line_taxes['taxes']:
|
||||
taxes.setdefault(tax['id'], {'name': tax['name'], 'total':0.0})
|
||||
taxes[tax['id']]['total'] += tax['amount']
|
||||
|
||||
st_line_ids = self.env["account.bank.statement.line"].search([('pos_statement_id', 'in', orders.ids)]).ids
|
||||
if st_line_ids:
|
||||
self.env.cr.execute("""
|
||||
SELECT aj.name, sum(amount) total
|
||||
FROM account_bank_statement_line AS absl,
|
||||
account_bank_statement AS abs,
|
||||
account_journal AS aj
|
||||
WHERE absl.statement_id = abs.id
|
||||
AND abs.journal_id = aj.id
|
||||
AND absl.id IN %s
|
||||
GROUP BY aj.name
|
||||
""", (tuple(st_line_ids),))
|
||||
payments = self.env.cr.dictfetchall()
|
||||
else:
|
||||
payments = []
|
||||
|
||||
return {
|
||||
'total_paid': user_currency.round(total),
|
||||
'payments': payments,
|
||||
'company_name': self.env.user.company_id.name,
|
||||
'taxes': taxes.values(),
|
||||
'products': [{
|
||||
'product_id': product.id,
|
||||
'product_name': product.name[:20],
|
||||
'code': product.default_code,
|
||||
'quantity': qty,
|
||||
'price_unit': price_unit,
|
||||
'discount': discount,
|
||||
'uom': product.uom_id.name
|
||||
} for (product, price_unit, discount), qty in products_sold.items()]
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def render_html(self, data=None):
|
||||
company = request.env.user.company_id
|
||||
date_start = self.env.context.get('date_start', False)
|
||||
date_stop = self.env.context.get('date_stop', False)
|
||||
data = dict(data or {}, date_start=date_start, date_stop=date_stop)
|
||||
data.update(self.get_sale_details(date_start, date_stop, company))
|
||||
return self.env['report'].render('point_of_sale.report_saledetails', data)
|
||||
@@ -406,6 +406,23 @@ var ProxyStatusWidget = StatusWidget.extend({
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
/* --------- The Sale Details --------- */
|
||||
|
||||
// Generates a report to print the sales of the
|
||||
// day on a ticket
|
||||
|
||||
var SaleDetailsButton = PosBaseWidget.extend({
|
||||
template: 'SaleDetailsButton',
|
||||
start: function(){
|
||||
var self = this;
|
||||
this.$el.click(function(){
|
||||
self.pos.proxy.print_sale_details();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
/*--------------------------------------*\
|
||||
| THE CHROME |
|
||||
\*======================================*/
|
||||
@@ -662,6 +679,11 @@ var Chrome = PosBaseWidget.extend({
|
||||
'name': 'order_selector',
|
||||
'widget': OrderSelectorWidget,
|
||||
'replace': '.placeholder-OrderSelectorWidget',
|
||||
},{
|
||||
'name': 'sale_details',
|
||||
'widget': SaleDetailsButton,
|
||||
'append': '.pos-rightheader',
|
||||
'condition': function(){ return this.pos.config.use_proxy; },
|
||||
},{
|
||||
'name': 'proxy_status',
|
||||
'widget': ProxyStatusWidget,
|
||||
@@ -769,6 +791,7 @@ return {
|
||||
HeaderButtonWidget: HeaderButtonWidget,
|
||||
OrderSelectorWidget: OrderSelectorWidget,
|
||||
ProxyStatusWidget: ProxyStatusWidget,
|
||||
SaleDetailsButton: SaleDetailsButton,
|
||||
StatusWidget: StatusWidget,
|
||||
SynchNotificationWidget: SynchNotificationWidget,
|
||||
UsernameWidget: UsernameWidget,
|
||||
|
||||
@@ -2,8 +2,10 @@ odoo.define('point_of_sale.devices', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var Model = require('web.DataModel');
|
||||
var Session = require('web.Session');
|
||||
|
||||
var QWeb = core.qweb;
|
||||
var _t = core._t;
|
||||
|
||||
// the JobQueue schedules a sequence of 'jobs'. each job is
|
||||
@@ -434,6 +436,23 @@ var ProxyDevice = core.Class.extend(core.mixins.PropertiesMixin,{
|
||||
send_printing_job();
|
||||
},
|
||||
|
||||
print_sale_details: function() {
|
||||
var self = this;
|
||||
new Model('report.point_of_sale.report_saledetails').call('get_sale_details').then(function(result){
|
||||
var env = {
|
||||
company: self.pos.company,
|
||||
pos: self.pos,
|
||||
products: result.products,
|
||||
payments: result.payments,
|
||||
taxes: result.taxes,
|
||||
total_paid: result.total_paid,
|
||||
date: (new Date()).toLocaleString(),
|
||||
};
|
||||
var report = QWeb.render('SaleDetailsReport', env);
|
||||
self.print_receipt(report);
|
||||
})
|
||||
},
|
||||
|
||||
// asks the proxy to log some information, as with the debug.log you can provide several arguments.
|
||||
log: function(){
|
||||
return this.message('log',{'arguments': _.toArray(arguments)});
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
odoo.define('point_of_sale.report', function (require) {
|
||||
"use strict";
|
||||
|
||||
var chat_manager = require('mail.chat_manager');
|
||||
var composer = require('mail.composer');
|
||||
var ChatThread = require('mail.ChatThread');
|
||||
var utils = require('mail.utils');
|
||||
|
||||
var config = require('web.config');
|
||||
var core = require('web.core');
|
||||
var data = require('web.data');
|
||||
var Dialog = require('web.Dialog');
|
||||
var framework = require('web.framework');
|
||||
var Model = require('web.Model');
|
||||
|
||||
var pyeval = require('web.pyeval');
|
||||
var SearchView = require('web.SearchView');
|
||||
|
||||
var ActionManager = require('web.ActionManager');
|
||||
var ControlPanelMixin = require('web.ControlPanelMixin');
|
||||
var datepicker = require('web.datepicker');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
var QWeb = core.qweb;
|
||||
var _t = core._t;
|
||||
|
||||
// TODO replace in v10 with proper wizard
|
||||
var PosDetailsWidget = Widget.extend(ControlPanelMixin, {
|
||||
template: 'WizardSaleDetailsReport',
|
||||
events: {
|
||||
"click .js_generate": "renderReport",
|
||||
},
|
||||
|
||||
renderElement: function() {
|
||||
this._super();
|
||||
|
||||
var current_date = new moment();
|
||||
this.dp_start = new datepicker.DateWidget(this);
|
||||
this.dp_start.insertBefore(this.$el.find('.js_date_start'));
|
||||
this.dp_start.set_value(current_date.format('YYYY-MM-DD'));
|
||||
|
||||
this.dp_stop = new datepicker.DateWidget(this);
|
||||
this.dp_stop.insertAfter(this.$el.find('.js_date_stop'));
|
||||
this.dp_stop.set_value(current_date.format('YYYY-MM-DD'));
|
||||
|
||||
},
|
||||
|
||||
renderReport: function() {
|
||||
var start = this.dp_start.get_value();
|
||||
var stop = this.dp_stop.get_value();
|
||||
var url = '/pos/sale_details_report?date_start='+start+'&date_stop='+stop;
|
||||
window.open(url, '_blank');
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
core.action_registry.add('report_pos_details', PosDetailsWidget);
|
||||
|
||||
return PosDetailsWidget;
|
||||
|
||||
});
|
||||
@@ -84,6 +84,14 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="SaleDetailsButton">
|
||||
<div class="oe_status">
|
||||
<div class="js_connected oe_icon">
|
||||
<i class='fa fa-fw fa-print'></i>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="HeaderButtonWidget">
|
||||
<div class="header-button">
|
||||
<t t-esc="widget.label" />
|
||||
@@ -829,6 +837,114 @@
|
||||
</receipt>
|
||||
</t>
|
||||
|
||||
<t t-name="SaleDetailsReport">
|
||||
<receipt align='center' width='40' value-thousands-separator='' >
|
||||
<t t-if='pos.company_logo_base64'>
|
||||
<img t-att-src='pos.company_logo_base64' />
|
||||
<br/>
|
||||
</t>
|
||||
<t t-if='!pos.company_logo_base64'>
|
||||
<h1><t t-esc='company.name' /></h1>
|
||||
<br/>
|
||||
</t>
|
||||
<br /><br />
|
||||
|
||||
<!-- Orderlines -->
|
||||
|
||||
<div class='orderlines' line-ratio='0.6'>
|
||||
<t t-foreach='products' t-as='line'>
|
||||
<line>
|
||||
<left><t t-esc='line.product_name' /></left>
|
||||
<right>
|
||||
<value value-decimals='2' value-autoint='on'>
|
||||
<t t-esc='line.quantity' />
|
||||
</value>
|
||||
<t t-if='line.uom !== "Unit(s)"'>
|
||||
<t t-esc='line.uom' />
|
||||
</t>
|
||||
</right>
|
||||
<right>
|
||||
<value><t t-esc='line.price_unit' /></value>
|
||||
</right>
|
||||
</line>
|
||||
<t t-if='line.discount !== 0'>
|
||||
<line indent='1'><left>Discount: <t t-esc='line.discount' />%</left></line>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<line><left>------------------------</left></line>
|
||||
<br/>
|
||||
|
||||
<line>
|
||||
<left>Payments:</left>
|
||||
</line>
|
||||
<line t-foreach='payments' t-as='payment'>
|
||||
<left><t t-esc='payment.name' /></left>
|
||||
<right>
|
||||
<t t-esc='payment.total'/>
|
||||
</right>
|
||||
</line>
|
||||
|
||||
<br/>
|
||||
<line><left>------------------------</left></line>
|
||||
<br/>
|
||||
|
||||
<line>
|
||||
<left>Taxes:</left>
|
||||
</line>
|
||||
<line t-foreach='taxes' t-as='taxe'>
|
||||
<left><t t-esc='taxe.name' /></left>
|
||||
<right>
|
||||
<t t-esc='taxe.total'/>
|
||||
</right>
|
||||
</line>
|
||||
|
||||
<br/>
|
||||
<line><left>------------------------</left></line>
|
||||
<br/>
|
||||
|
||||
<line>
|
||||
<left>Total:</left>
|
||||
<right>
|
||||
<t t-esc='total_paid'/>
|
||||
</right>
|
||||
</line>
|
||||
|
||||
<br/>
|
||||
<div font='b'>
|
||||
<div><t t-esc='date' /></div>
|
||||
</div>
|
||||
|
||||
</receipt>
|
||||
</t>
|
||||
|
||||
|
||||
<t t-name="WizardSaleDetailsReport">
|
||||
<div class="o_form_view"><div class="oe_form_sheetbg o_form_sheet_bg"><div class="oe_form_sheet oe_form_sheet_width o_form_sheet">
|
||||
<div class="pos_details_dates">
|
||||
<h3>Select Dates for the report</h3>
|
||||
<table class="table-condensed">
|
||||
<tr>
|
||||
<td><strong>Start date</strong></td>
|
||||
<td><div class="js_date_start"></div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Stop date</strong></td>
|
||||
<td><div class="js_date_stop"></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
<p>
|
||||
<button class="js_generate">Generate report</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div></div></div>
|
||||
</t>
|
||||
|
||||
|
||||
<t t-name="PopupWidget">
|
||||
<div class="modal-dialog">
|
||||
<div class="popup popup-alert">
|
||||
|
||||
@@ -17,5 +17,11 @@
|
||||
<field name="report_name">point_of_sale.report_invoice</field>
|
||||
</record>
|
||||
|
||||
<record id="sale_details_report" model="ir.actions.report.xml">
|
||||
<field name="name">Sale Details</field>
|
||||
<field name="model">report.point_of_sale.details</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">point_of_sale.report_saledetails</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
<template id="assets_backend" name="hr assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<link rel="stylesheet" href="/point_of_sale/static/src/less/pos_dashboard.less"/>
|
||||
|
||||
<script type="text/javascript" src="/point_of_sale/static/src/js/report.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
|
||||
@@ -68,5 +68,11 @@
|
||||
<field name="context">{'group_by_no_leaf':1,'group_by':[]}</field>
|
||||
</record>
|
||||
|
||||
<record id="action_report_pos_details" model="ir.actions.client">
|
||||
<field name="name">Sale Details</field>
|
||||
<field name="tag">report_pos_details</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_report_pos_order_all" name="Orders" action="action_report_pos_order_all" parent="menu_point_rep" sequence="3"/>
|
||||
<menuitem id="menu_report_order_details" name="Sale Details" action="action_report_pos_details" parent="menu_point_rep" sequence="4"/>
|
||||
</odoo>
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<template id="report_saledetails">
|
||||
<t t-call="report.html_container">
|
||||
<t t-call="report.internal_layout">
|
||||
<div class="page">
|
||||
|
||||
<div class="text-center">
|
||||
<h2>Sale Details</h2>
|
||||
|
||||
<strong><t t-esc="date_start"/> - <t t-esc="date_stop"/></strong>
|
||||
</div>
|
||||
|
||||
<!-- Orderlines -->
|
||||
<h3>Products</h3>
|
||||
<table class="table table-condensed">
|
||||
<thead><tr>
|
||||
<th>Product</th>
|
||||
<th>Quantity</th>
|
||||
<th>Price Unit</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr t-foreach='products' t-as='line'>
|
||||
<td><t t-esc="line['product_name']" /></td>
|
||||
<td>
|
||||
<t t-esc="line['quantity']" />
|
||||
<t t-if='line["uom"] != "Unit(s)"'>
|
||||
<t t-esc='line["uom"]' />
|
||||
</t>
|
||||
</td>
|
||||
<td>
|
||||
<t t-esc='line["price_unit"]' />
|
||||
<t t-if='line["discount"] != 0'>
|
||||
Disc: <t t-esc='line["discount"]' />%
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
|
||||
<h3>Payments</h3>
|
||||
<table class="table table-condensed">
|
||||
<thead><tr>
|
||||
<th>Name</th>
|
||||
<th>Total</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr t-foreach='payments' t-as='payment'>
|
||||
<td><t t-esc="payment['name']" /></td>
|
||||
<td><t t-esc="payment['total']" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
|
||||
<h3>Taxes</h3>
|
||||
<table class="table table-condensed">
|
||||
<thead><tr>
|
||||
<th>Name</th>
|
||||
<th>Total</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr t-foreach='taxes' t-as='tax'>
|
||||
<td><t t-esc="tax['name']" /></td>
|
||||
<td><t t-esc="tax['total']" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<strong>Total: <t t-esc='total_paid'/></strong>
|
||||
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
</data>
|
||||
</openerp>
|
||||
Reference in New Issue
Block a user