Browse Source

[ADD] Initial Commit

pull/30/merge
SHEREEF PT 8 years ago
parent
commit
43b6f3946b
  1. 44
      pos_quotation_order/README.rst
  2. 23
      pos_quotation_order/__init__.py
  3. 43
      pos_quotation_order/__manifest__.py
  4. 4
      pos_quotation_order/models/__init__.py
  5. 211
      pos_quotation_order/models/pos_quotation.py
  6. 3
      pos_quotation_order/security/ir.model.access.csv
  7. BIN
      pos_quotation_order/static/description/banner.jpg
  8. BIN
      pos_quotation_order/static/description/cybro_logo.png
  9. BIN
      pos_quotation_order/static/description/icon.png
  10. 201
      pos_quotation_order/static/description/index.html
  11. BIN
      pos_quotation_order/static/description/quot_01.png
  12. BIN
      pos_quotation_order/static/description/quot_02.png
  13. BIN
      pos_quotation_order/static/description/quot_03.png
  14. BIN
      pos_quotation_order/static/description/quot_04.png
  15. BIN
      pos_quotation_order/static/description/quot_05.png
  16. BIN
      pos_quotation_order/static/description/quot_06.png
  17. BIN
      pos_quotation_order/static/description/quot_07.png
  18. BIN
      pos_quotation_order/static/description/quot_08.png
  19. BIN
      pos_quotation_order/static/description/quot_09.png
  20. BIN
      pos_quotation_order/static/description/quot_10.png
  21. BIN
      pos_quotation_order/static/description/quot_11.png
  22. 51
      pos_quotation_order/static/src/css/pos_quotation.css
  23. 102
      pos_quotation_order/static/src/js/models.js
  24. 243
      pos_quotation_order/static/src/js/pos_quotation.js
  25. 144
      pos_quotation_order/static/src/xml/pos_quotation.xml
  26. 122
      pos_quotation_order/views/pos_quotation.xml
  27. 12
      pos_quotation_order/views/quotation_templates.xml

44
pos_quotation_order/README.rst

@ -0,0 +1,44 @@
========================
Pos Quotation Orders V10
========================
This module allows to create and process quotation orders from POS screen.
Installation
============
Just select it from available modules to install it, there is no need to extra installations.
Configuration
=============
Nothing to configure.
Features
========
* 'Create Quotation' button in POS.
* Create pos quotations.
* 'Quotation List' button in POS.
* Quotation list window.
* Manage quotations from POS.
* Quotation reference in order receipt and order.
Usage
=====
* We cannot create quotation order if there is no order lines added.
* Otherwise it will raise an alert popup with invalid order line.
* On confirming Quotation creation with invalid order date, display an error message.
* On confirming Order Creation, create quotation with order lines and it shows popup with quotation reference.
* You can view all draft quotations on 'Quotation List' button click.
* There is a confirm option for each quotations.
* On confirming quotation, order lines and customer assigned with quotation details.
Credits
=======
Developer: Aswani pc @ cybrosys

23
pos_quotation_order/__init__.py

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
# Copyright (C) 2009-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
# Author: Aswani PC(<https://www.cybrosys.com>)
# you can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# It is forbidden to publish, distribute, sublicense, or sell copies
# of the Software or modified copies of the Software.
#
# 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# GENERAL PUBLIC LICENSE (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import models

43
pos_quotation_order/__manifest__.py

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
# Copyright (C) 2009-TODAY Cybrosys Technologies(<https://www.cybrosys.com>).
# Author: Aswani PC(<https://www.cybrosys.com>)
# you can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# It is forbidden to publish, distribute, sublicense, or sell copies
# of the Software or modified copies of the Software.
#
# 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# GENERAL PUBLIC LICENSE (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': "Pos Quotation Orders",
'version': '10.0.1.0.0',
'summary': """Create & Process Quotation from POS""",
'description': """This module allows to create and process quotation orders from POS.""",
'author': "Cybrosys Techno Solutions",
'company': "Cybrosys Techno Solutions",
'website': "http://www.cybrosys.com",
'category': 'Point of Sale',
'depends': ['base', 'point_of_sale'],
'data': [
'security/ir.model.access.csv',
'views/quotation_templates.xml',
'views/pos_quotation.xml',
],
'qweb': ['static/src/xml/pos_quotation.xml'],
'images': ['static/description/banner.jpg'],
'license': 'AGPL-3',
'installable': True,
'auto_install': False,
}

4
pos_quotation_order/models/__init__.py

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import pos_quotation

211
pos_quotation_order/models/pos_quotation.py

@ -0,0 +1,211 @@
# -*- coding: utf-8 -*-
import logging
import psycopg2
from functools import partial
from odoo import models, fields, api, tools, _
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
class PosOrder(models.Model):
_inherit = 'pos.order'
quot_ref = fields.Many2one('pos.quotation', string='Quotation Ref')
@api.model
def create_from_ui(self, orders):
# Keep only new orders
submitted_references = [o['data']['name'] for o in orders]
pos_order = self.search([('pos_reference', 'in', submitted_references)])
existing_orders = pos_order.read(['pos_reference'])
existing_references = set([o['pos_reference'] for o in existing_orders])
orders_to_save = [o for o in orders if o['data']['name'] not in existing_references]
order_ids = []
quot_ids = []
for tmp_order in orders_to_save:
to_invoice = tmp_order['to_invoice']
order = tmp_order['data']
if to_invoice:
self._match_payment_to_invoice(order)
pos_order = self._process_order(order)
if pos_order.quot_ref:
pos_order.quot_ref.write({'state': 'confirmed'})
quot_ids.append(pos_order.quot_ref.id)
order_ids.append(pos_order.id)
try:
pos_order.action_pos_order_paid()
except psycopg2.OperationalError:
# do not hide transactional errors, the order(s) won't be saved!
raise
except Exception as e:
_logger.error('Could not fully process the POS Order: %s', tools.ustr(e))
if to_invoice:
pos_order.action_pos_order_invoice()
pos_order.invoice_id.sudo().action_invoice_open()
pos_order.account_move = pos_order.invoice_id.move_id
return order_ids, quot_ids
@api.model
def _order_fields(self, ui_order):
process_line = partial(self.env['pos.order.line']._order_line_fields)
quot_id = False
if 'quotation_ref' in ui_order:
if ui_order['quotation_ref']:
quot_id = ui_order['quotation_ref']['id']
return {
'name': ui_order['name'],
'quot_ref': quot_id,
'user_id': ui_order['user_id'] or False,
'session_id': ui_order['pos_session_id'],
'lines': [process_line(l) for l in ui_order['lines']] if ui_order['lines'] else False,
'pos_reference': ui_order['name'],
'partner_id': ui_order['partner_id'] or False,
'date_order': ui_order['creation_date'],
'fiscal_position_id': ui_order['fiscal_position_id']
}
class PosQuotation(models.Model):
_name = 'pos.quotation'
@api.model
def _amount_line_tax(self, line, fiscal_position_id):
taxes = line.tax_ids.filtered(lambda t: t.company_id.id == line.order_id.company_id.id)
if fiscal_position_id:
taxes = fiscal_position_id.map_tax(taxes, line.product_id, line.order_id.partner_id)
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
taxes = taxes.compute_all(price, line.order_id.pricelist_id.currency_id, line.qty, product=line.product_id,
partner=line.order_id.partner_id or False)['taxes']
return sum(tax.get('amount', 0.0) for tax in taxes)
@api.model
def _order_fields(self, ui_order):
process_line = partial(self.env['pos.quotation.line']._order_line_fields)
return {
'lines': [process_line(l) for l in ui_order['lines']] if ui_order['lines'] else False,
'partner_id': ui_order['partner_id'] or False,
'date_order': ui_order['date_order'],
'note': ui_order['note'] or '',
}
def _default_session(self):
return self.env['pos.session'].search([('state', '=', 'opened'), ('user_id', '=', self.env.uid)], limit=1)
def _default_pricelist(self):
return self._default_session().config_id.pricelist_id
name = fields.Char(string='Order Ref', required=True, readonly=True, copy=False, default='/')
company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True,
default=lambda self: self.env.user.company_id)
date_quotation = fields.Datetime(string='Quotation Date', readonly=True, index=True, default=fields.Datetime.now)
date_order = fields.Date(string='Order Date', readonly=True, index=True)
amount_tax = fields.Float(compute='_compute_amount_all', string='Taxes', digits=0)
amount_total = fields.Float(compute='_compute_amount_all', string='Total', digits=0)
lines = fields.One2many('pos.quotation.line', 'order_id', string='Order Lines', copy=True)
pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', default=_default_pricelist)
partner_id = fields.Many2one('res.partner', string='Customer', change_default=True, index=True)
state = fields.Selection([('draft', 'New'), ('confirmed', 'Confirmed')], 'Status', readonly=True, copy=False, default='draft')
note = fields.Text(string='Internal Notes')
fiscal_position_id = fields.Many2one('account.fiscal.position', string='Fiscal Position')
@api.depends('lines.price_subtotal_incl', 'lines.discount')
def _compute_amount_all(self):
for order in self:
order.amount_tax = 0.0
currency = order.pricelist_id.currency_id
order.amount_tax = currency.round(
sum(self._amount_line_tax(line, order.fiscal_position_id) for line in order.lines))
amount_untaxed = currency.round(sum(line.price_subtotal for line in order.lines))
order.amount_total = order.amount_tax + amount_untaxed
@api.model
def create_from_ui(self, orders):
order_id = self.create(self._order_fields(orders))
order = {'id': order_id.id,
'name': order_id.name}
return order
@api.model
def create(self, vals):
if vals.get('name', '/') == '/':
vals['name'] = self.env['ir.sequence'].next_by_code('pos.quotation') or '/'
return super(PosQuotation, self).create(vals)
class PosQuotationLine(models.Model):
_name = "pos.quotation.line"
_description = "Lines of Point of Sale"
_rec_name = "product_id"
def _order_line_fields(self, line):
if line and 'tax_ids' not in line[2]:
product = self.env['product.product'].browse(line[2]['product_id'])
line[2]['tax_ids'] = [(6, 0, [x.id for x in product.taxes_id])]
return line
company_id = fields.Many2one('res.company', string='Company', required=True,
default=lambda self: self.env.user.company_id)
name = fields.Char(string='Line No')
notice = fields.Char(string='Discount Notice')
product_id = fields.Many2one('product.product', string='Product', domain=[('sale_ok', '=', True)],
required=True, change_default=True)
price_unit = fields.Float(string='Unit Price', digits=0)
qty = fields.Float('Quantity', default=1)
price_subtotal = fields.Float(compute='_compute_amount_line_all', digits=0, string='Subtotal w/o Tax')
price_subtotal_incl = fields.Float(compute='_compute_amount_line_all', digits=0, string='Subtotal')
discount = fields.Float(string='Discount (%)', digits=0, default=0.0)
order_id = fields.Many2one('pos.quotation', string='Order Ref', ondelete='cascade')
create_date = fields.Datetime(string='Creation Date', readonly=True)
tax_ids = fields.Many2many('account.tax', string='Taxes', readonly=True)
tax_ids_after_fiscal_position = fields.Many2many('account.tax',string='Taxes')
pack_lot_ids = fields.One2many('pos.pack.operation.lot', 'pos_order_line_id', string='Lot/serial Number')
@api.depends('price_unit', 'tax_ids', 'qty', 'discount', 'product_id')
def _compute_amount_line_all(self):
for line in self:
currency = line.order_id.pricelist_id.currency_id
taxes = line.tax_ids.filtered(lambda tax: tax.company_id.id == line.order_id.company_id.id)
fiscal_position_id = line.order_id.fiscal_position_id
if fiscal_position_id:
taxes = fiscal_position_id.map_tax(taxes, line.product_id, line.order_id.partner_id)
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
line.price_subtotal = line.price_subtotal_incl = price * line.qty
if taxes:
taxes = taxes.compute_all(price, currency, line.qty, product=line.product_id,
partner=line.order_id.partner_id or False)
line.price_subtotal = taxes['total_excluded']
line.price_subtotal_incl = taxes['total_included']
line.price_subtotal = currency.round(line.price_subtotal)
line.price_subtotal_incl = currency.round(line.price_subtotal_incl)
@api.onchange('product_id')
def _onchange_product_id(self):
if self.product_id:
if not self.order_id.pricelist_id:
raise UserError(
_('You have to select a pricelist in the sale form !\n'
'Please set one before choosing a product.'))
price = self.order_id.pricelist_id.get_product_price(
self.product_id, self.qty or 1.0, self.order_id.partner_id)
self._onchange_qty()
self.price_unit = price
self.tax_ids = self.product_id.taxes_id
@api.onchange('qty', 'discount', 'price_unit', 'tax_ids')
def _onchange_qty(self):
if self.product_id:
if not self.order_id.pricelist_id:
raise UserError(_('You have to select a pricelist in the sale form !'))
price = self.price_unit * (1 - (self.discount or 0.0) / 100.0)
self.price_subtotal = self.price_subtotal_incl = price * self.qty
if self.product_id.taxes_id:
taxes = self.product_id.taxes_id.compute_all(price, self.order_id.pricelist_id.currency_id, self.qty,
product=self.product_id, partner=False)
self.price_subtotal = taxes['total_excluded']
self.price_subtotal_incl = taxes['total_included']

3
pos_quotation_order/security/ir.model.access.csv

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_pos_quotation,pos.quotation,model_pos_quotation,point_of_sale.group_pos_user,1,1,1,1
access_pos_quotation_line,pos.quotation.line,model_pos_quotation_line,point_of_sale.group_pos_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_pos_quotation pos.quotation model_pos_quotation point_of_sale.group_pos_user 1 1 1 1
3 access_pos_quotation_line pos.quotation.line model_pos_quotation_line point_of_sale.group_pos_user 1 1 1 1

BIN
pos_quotation_order/static/description/banner.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

BIN
pos_quotation_order/static/description/cybro_logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
pos_quotation_order/static/description/icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

201
pos_quotation_order/static/description/index.html

@ -0,0 +1,201 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Pos Quotation Orders</h2>
<h3 class="oe_slogan">Create And Process Quotation from POS</h3>
<h4 class="oe_slogan"><a href="https://www.cybrosys.com">Cybrosys Technologies</a></h4>
</div>
<div class="oe_row oe_spaced" style="padding-left:65px;">
<h4>Features:</h4>
<div>
<span style="color:green;"> &#9745; </span>'Create Quotation' button in POS.<br/>
<span style="color:green;"> &#9745; </span>Create pos quotations.<br/>
<span style="color:green;"> &#9745; </span>'Quotation List' button in POS.<br/>
<span style="color:green;"> &#9745; </span>Quotation list window.<br/>
<span style="color:green;"> &#9745; </span>Manage quotations from POS.<br/>
<span style="color:green;"> &#9745; </span>Quotation reference in order receipt and order.<br/>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="oe_picture">
<h3 class="oe_slogan">Overview</h3>
<p class="oe_mt32">
POS Quotation Orders is a plugin that facilitates the creation and management of Quotation orders from POS window. By default, you can’t create a quotation order from POS window in Odoo. But in some cases, such a simple option can make considerable changes in your sales. This plugin will add a ‘Create Quotation’ in POS window and will facilitate the Quotation creation process without affecting the normal workflow of POS.
</p>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="" style="text-align: center;">
<p>After installation, open POS window and start and new Session.</p>
<div class="oe_span12">
<div class="oe_demo oe_picture oe_screenshot">
<img style="border:10px solid white;" src="quot_01.png">
</div>
</div>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="" style="text-align: center;">
<p>In order to create a quotation you have to add products in Order line. Otherwise it will raise an alert popup with invalid order line.</p>
<div class="oe_span12">
<div class="oe_demo oe_picture oe_screenshot">
<img style="border:10px solid white;" src="quot_02.png">
</div>
</div>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<h3 class="oe_slogan">Quotation Popup</h3>
<div class="" style="text-align: center;">
<div class="oe_span12">
<p>Add the products in order line and click ‘Create Quotation’ it will give you a popup to add order date and a note for quotation.</p>
<div class="oe_demo oe_picture oe_screenshot">
<img style="border:10px solid white;" src="quot_03.png">
</div>
</div>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<h3 class="oe_slogan">Quotation Reference</h3>
<div class="" style="text-align: center;">
<div class="oe_span12">
<p>Once you have successfully created a quotation, you will get a Quotation reference number as in the figure.</p>
<div class="oe_demo oe_picture oe_screenshot">
<img style="border:10px solid white;" src="quot_04.png">
</div>
</div>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<h3 class="oe_slogan">Created POS Quotation View</h3>
<div class="" style="text-align: center;">
<div class="oe_span12">
<p>You can view and manage the created PoS quotations from </p>
<br/>
<p>Point of Sale -> Orders -> Quotation</p>
<div class="oe_demo oe_picture oe_screenshot">
<img style="border:10px solid white;" src="quot_05.png">
</div>
<p>Click on any of the quotation to view the details of the particular quotation.</p>
<div class="oe_demo oe_picture oe_screenshot">
<img style="border:10px solid white;" src="quot_06.png">
</div>
</div>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="" style="text-align: center;">
<div class="oe_span12">
<h3 class="oe_slogan">Manage Quotation</h3>
<p>You can view all ‘Draft’ quotation by clicking ‘Quotation List' button in POS. Click the corresponding ‘Confirm’ button.</p>
<div class="oe_demo oe_picture oe_screenshot">
<img style="border:10px solid white;" src="quot_07.png">
</div>
</div>
</div>
</div>
</section>
<section class="oe_container ">
<div class="oe_row oe_spaced">
<div>
<div class="oe_span6">
<div class="oe_row_img oe_centered">
<img style="border:10px solid white;" class="oe_picture oe_screenshot" src="quot_08.png">
</div>
</div>
<br><br><br><br><br><br>
<p>On confirming quotation, you can view the order lines and customer assigned with quotation details.</p>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="" style="text-align: center;">
<div class="oe_span12">
<h3 class="oe_slogan">POS Receipt with Quotation Reference</h3>
<div class="oe_span6 oe_centered">
<div class="oe_row_img ">
<img style="border:10px solid white;" class="oe_picture oe_screenshot" src="quot_09.png">
</div>
</div>
</div>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="" style="text-align: center;">
<div class="oe_span12">
<h3 class="oe_slogan">Quotation status</h3>
<p>Now, if you go to the quotation details you can see that the Quotation status is changed to ‘confirmed’.</p>
<div class="oe_demo oe_picture oe_screenshot">
<img style="border:10px solid white;" src="quot_10.png">
</div>
</div>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="" style="text-align: center;">
<div class="oe_span12">
<h3 class="oe_slogan">POS Order with Quotation Reference</h3>
<div class="oe_span5">
<div class="oe_demo oe_picture oe_screenshot">
<img style="border:10px solid white;" src="quot_11.png">
</div>
</div>
</div>
</div>
</div>
</section>
<section class="oe_container">
<h2 class="oe_slogan" style="margin-top:20px;" >Need Any Help?</h2>
<div class="oe_slogan" style="margin-top:10px !important;">
<div>
<a class="btn btn-primary btn-lg mt8"
style="color: #FFFFFF !important;border-radius: 0;" href="https://www.cybrosys.com"><i
class="fa fa-envelope"></i> Email </a> <a
class="btn btn-primary btn-lg mt8" style="color: #FFFFFF !important;border-radius: 0;"
href="https://www.cybrosys.com/contact/"><i
class="fa fa-phone"></i> Contact Us </a> <a
class="btn btn-primary btn-lg mt8" style="color: #FFFFFF !important;border-radius: 0;"
href="https://www.cybrosys.com/odoo-customization-and-installation/"><i
class="fa fa-check-square"></i> Request Customization </a>
</div>
<br>
<img src="cybro_logo.png" style="width: 190px; margin-bottom: 20px;" class="center-block">
<div>
<a href="https://twitter.com/cybrosys" target="_blank"><i class="fa fa-2x fa-twitter" style="color:white;background: #00a0d1;width:35px;"></i></a></td>
<a href="https://www.linkedin.com/company/cybrosys-technologies-pvt-ltd" target="_blank"><i class="fa fa-2x fa-linkedin" style="color:white;background: #31a3d6;width:35px;padding-left: 3px;"></i></a></td>
<a href="https://www.facebook.com/cybrosystechnologies" target="_blank"><i class="fa fa-2x fa-facebook" style="color:white;background: #3b5998;width:35px;padding-left: 8px;"></i></a></td>
<a href="https://plus.google.com/106641282743045431892/about" target="_blank"><i class="fa fa-2x fa-google-plus" style="color:white;background: #c53c2c;width:35px;padding-left: 3px;"></i></a></td>
<a href="https://in.pinterest.com/cybrosys" target="_blank"><i class="fa fa-2x fa-pinterest" style="color:white;background: #ac0f18;width:35px;padding-left: 3px;"></i></a></td>
</div>
</div>
</section>

BIN
pos_quotation_order/static/description/quot_01.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

BIN
pos_quotation_order/static/description/quot_02.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

BIN
pos_quotation_order/static/description/quot_03.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

BIN
pos_quotation_order/static/description/quot_04.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

BIN
pos_quotation_order/static/description/quot_05.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
pos_quotation_order/static/description/quot_06.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

BIN
pos_quotation_order/static/description/quot_07.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
pos_quotation_order/static/description/quot_08.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
pos_quotation_order/static/description/quot_09.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
pos_quotation_order/static/description/quot_10.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
pos_quotation_order/static/description/quot_11.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

51
pos_quotation_order/static/src/css/pos_quotation.css

@ -0,0 +1,51 @@
.alert_msg{
color:red;
}
.popup_quot_ref{
height: 150px !important;
}
/* The Quotation List Screen */
.quotation_list-screen .quotation-list{
font-size: 16px;
width: 100%;
line-height: 40px;
}
.quotation_list-screen .quotation-list th,
.quotation_list-screen .quotation-list td {
padding: 0px 8px;
}
.quotation_list-screen .quotation-list tr{
transition: all 150ms linear;
background: rgb(230,230,230);
}
.quotation_list-screen .quotation-list thead > tr,
.quotation_list-screen .quotation-list tr:nth-child(even) {
background: rgb(247,247,247);
}
.quotation_list-screen .quotation-list tr.highlight{
transition: all 150ms linear;
background: rgb(110,200,155) !important;
color: white;
}
.quotation_list-screen .quotation-list td tr.lowlight{
transition: all 150ms linear;
background: rgb(216, 238, 227);
}
.quotation_list-screen .quotation-list tr.lowlight:nth-child(even){
transition: all 150ms linear;
background: rgb(227, 246, 237);
}
.quotation_list-screen .searchbox{
right: auto;
margin-left: -90px;
margin-top:8px;
left: 50%;
}
.quotation_list-screen .searchbox input{
width: 120px;
}

102
pos_quotation_order/static/src/js/models.js

@ -0,0 +1,102 @@
odoo.define('pos_quotation_order.models', function (require) {
"use strict";
var screens = require('point_of_sale.screens');
var gui = require('point_of_sale.gui');
var core = require('web.core');
var models = require('point_of_sale.models');
var Model = require('web.DataModel');
var QWeb = core.qweb;
var _t = core._t;
models.load_models({
model: 'pos.quotation',
fields: ['name', 'partner_id','date_order','amount_total','lines','state'],
domain: [['state','=','draft']],
loaded: function(self, quotations){
self.quotations = quotations;
}
});
models.load_models({
model: 'pos.quotation.line',
fields: ['product_id', 'qty'],
loaded: function(self, quotation_lines){
self.quotation_lines = quotation_lines;
}
});
var _super_order = models.Order.prototype;
models.Order = models.Order.extend({
export_as_JSON: function() {
var data = _super_order.export_as_JSON.apply(this, arguments);
data.quotation_ref = this.quotation_ref;
return data;
},
init_from_JSON: function(json) {
this.quotation_ref = json.quotation_ref;
_super_order.init_from_JSON.call(this, json);
},
});
var posmodel_super = models.PosModel.prototype;
models.PosModel = models.PosModel.extend({
_save_to_server: function (orders, options) {
if (!orders || !orders.length) {
var result = $.Deferred();
result.resolve([]);
return result;
}
options = options || {};
var self = this;
var timeout = typeof options.timeout === 'number' ? options.timeout : 7500 * orders.length;
var order_ids_to_sync = _.pluck(orders, 'id');
var quot_model = new Model('pos.quotation');
var fields = _.find(this.models,function(model){ return model.model === 'pos.quotation'; }).fields;
var posOrderModel = new Model('pos.order');
return posOrderModel.call('create_from_ui',
[_.map(orders, function (order) {
order.to_invoice = options.to_invoice || false;
return order;
})],
undefined,
{
shadow: !options.to_invoice,
timeout: timeout
}
).then(function (server_ids) {
if (server_ids[1].length != 0){
for (var item in server_ids[1]){
quot_model.query(fields).filter([['id','=',server_ids[1][item]]]).first().then(function(quotation){
var index = self.quotations.indexOf(quotation);
self.quotations.splice(index, 1);
});
}
}
_.each(order_ids_to_sync, function (order_id) {
self.db.remove_order(order_id);
});
self.set('failed',false);
return server_ids[0];
}).fail(function (error, event){
if(error.code === 200 ){
if (error.data.exception_type == 'warning') {
delete error.data.debug;
}
if ((!self.get('failed') || options.show_error) && !options.to_invoice) {
self.gui.show_popup('error-traceback',{
'title': error.data.message,
'body': error.data.debug
});
}
self.set('failed',error)
}
event.preventDefault();
console.error('Failed to send orders:', orders);
});
},
});
});

243
pos_quotation_order/static/src/js/pos_quotation.js

@ -0,0 +1,243 @@
odoo.define('point_of_sale.pos_quotation_order', function (require) {
"use strict";
var screens = require('point_of_sale.screens');
var gui = require('point_of_sale.gui');
var Model = require('web.DataModel');
var core = require('web.core');
var PopupWidget = require('point_of_sale.popups');
var ProductListWidget = screens.ProductListWidget;
var ScreenWidget = screens.ScreenWidget;
var QWeb = core.qweb;
var _t = core._t;
var QuotationPopupWidget = PopupWidget.extend({
template: 'QuotationPopupWidget',
events: _.extend({}, PopupWidget.prototype.events,{
"keyup .order_date" : "date_validate",
}),
show: function(options){
options = options || {};
var self = this;
this._super(options);
this.renderElement();
},
date_validate: function(){
var v = $(".order_date").val();
if (v.match(/^\d{4}$/) !== null) {
$(".order_date").val(v + '/');
}
else if (v.match(/^\d{4}\/\d{2}$/) !== null) {
$(".order_date").val(v + '/');
}
},
click_confirm: function(){
var self = this;
var new_quotation = [];
var model = new Model('pos.quotation');
var line_model = new Model('pos.quotation.line');
var fields = _.find(this.pos.models,function(model){ return model.model === 'pos.quotation'; }).fields;
var line_fields = _.find(this.pos.models,function(model){ return model.model === 'pos.quotation.line'; }).fields;
var today = new Date().toJSON().slice(0,10);
var order = this.pos.get_order();
var order_to_save = order.export_as_JSON();
var order_lines = this.pos.get_order().get_orderlines();
var order_date = this.$('.order_date').val();
var order_note = this.$('.order_note').val();
var valid_date = true;
var validatePattern = /^(\d{4})([/|-])(\d{1,2})([/|-])(\d{1,2})$/;
if (order_date){
var dateValues = order_date.match(validatePattern);
if (dateValues == null){
valid_date = false;
}
else{
var orderYear = dateValues[1];
var orderMonth = dateValues[3];
var orderDate = dateValues[5];
if ((orderMonth < 1) || (orderMonth > 12)) {
valid_date = false;
}
else if ((orderDate < 1) || (orderDate> 31)) {
valid_date = false;
}
else if ((orderMonth==4 || orderMonth==6 || orderMonth==9 || orderMonth==11) && orderDate ==31) {
valid_date = false;
}
else if (orderMonth == 2){
var isleap = (orderYear % 4 == 0 && (orderYear % 100 != 0 || orderYear % 400 == 0));
if (orderDate> 29 || (orderDate ==29 && !isleap)){
valid_date = false;
}
}
var dates = [orderYear,orderMonth,orderDate];
order_date = dates.join('-');
}
}
$('.alert_msg').text("");
if (order_date && order_date < today || valid_date==false || !order_date){
$('.alert_msg').text("Please Select Valid Order Date!");
}
else{
$('.alert_msg').text("");
if (order_date){
order_to_save.date_order = order_date;
}
order_to_save.note = order_note;
model.call('create_from_ui',[order_to_save]).then(function(order){
model.query(fields).filter([['id','=',order['id']]]).first().then(function(quotation){
self.pos.quotations.push(quotation);
for (var line in quotation['lines']){
line_model.query(line_fields).filter([['id','=',quotation['lines'][line]]]).first().then(function(quotation_line){
self.pos.quotation_lines.push(quotation_line);
});
}
});
self.gui.close_popup();
self.pos.delete_current_order();
self.gui.show_popup('pos_quot_result',{
'body': _t('Quotation Ref : ')+ order['name'] ,
});
});
}
},
});
var QuotationListScreenWidget = ScreenWidget.extend({
template: 'QuotationListScreenWidget',
back_screen: 'product',
init: function(parent, options){
var self = this;
this._super(parent, options);
},
show: function(){
var self = this;
this._super();
this.renderElement();
this.$('.back').click(function(){
self.gui.back();
});
var quotations = this.pos.quotations;
this.render_list(quotations);
this.$('.quotation-list-contents').delegate('.quotation-line .confirm_quotation','click',function(event){
self.line_select(event,$(this.parentElement.parentElement),parseInt($(this.parentElement.parentElement).data('id')));
});
var search_timeout = null;
if(this.pos.config.iface_vkeyboard && this.chrome.widget.keyboard){
this.chrome.widget.keyboard.connect(this.$('.searchbox input'));
}
this.$('.searchbox input').on('keypress',function(event){
clearTimeout(search_timeout);
var query = this.value;
search_timeout = setTimeout(function(){
self.perform_search(query,event.which === 13);
},70);
});
this.$('.searchbox .search-clear').click(function(){
self.clear_search();
});
},
render_list: function(quotations){
var contents = this.$el[0].querySelector('.quotation-list-contents');
contents.innerHTML = "";
for(var i = 0, len = Math.min(quotations.length,1000); i < len; i++){
var quotation = quotations[i];
var quotation_line_html = QWeb.render('QuotationLine',{widget: this, quotation:quotations[i]});
var quotation_line = document.createElement('tbody');
quotation_line.innerHTML = quotation_line_html;
quotation_line = quotation_line.childNodes[1];
contents.appendChild(quotation_line);
}
},
line_select: function(event,$line,id){
var self = this;
var order = this.pos.get_order();
for (var quot_id in this.pos.quotations){
if (this.pos.quotations[quot_id]['id'] == id){
var selected_quotation = this.pos.quotations[quot_id]
}
}
if (selected_quotation){
for (var line in this.pos.quotation_lines){
if (selected_quotation['lines'].indexOf(this.pos.quotation_lines[line]['id']) > -1 ){
var product_id = this.pos.db.get_product_by_id(this.pos.quotation_lines[line]['product_id'][0]);
this.pos.get_order().add_product(product_id,{ quantity: this.pos.quotation_lines[line]['qty']});
}
}
order.quotation_ref = selected_quotation;
if (selected_quotation.partner_id){
var partner = this.pos.db.get_partner_by_id(selected_quotation.partner_id[0]);
order.set_client(partner);
}
this.gui.show_screen('products');
}
},
});
gui.define_popup({name:'pos_quot', widget: QuotationPopupWidget});
var QuotationResultPopupWidget = PopupWidget.extend({
template: 'QuotationResultPopupWidget',
});
gui.define_popup({name:'pos_quot_result', widget: QuotationResultPopupWidget});
gui.define_screen({name:'quotation_list', widget: QuotationListScreenWidget});
var QuotationListButton = screens.ActionButtonWidget.extend({
template: 'QuotationListButton',
button_click: function(){
this.gui.show_screen('quotation_list');
}
});
screens.define_action_button({
'name': 'pos_quotation_list',
'widget': QuotationListButton,
});
var QuotationButton = screens.ActionButtonWidget.extend({
template: 'QuotationButton',
button_click: function(){
var order_lines = this.pos.get_order().get_orderlines();
var flag_negative = false;
for (var line in order_lines){
if (order_lines[line].quantity < 0){
flag_negative = true;
}
}
if(this.pos.get_order().get_orderlines().length > 0 && flag_negative == false && this.pos.get_order().get_total_with_tax()>0){
this.gui.show_popup('pos_quot');
}
else if(flag_negative == true){
this.gui.show_popup('pos_quot_result',{
'body': _t('Invalid Order: Negative Quantity is Not Allowed'),
});
}
else if(this.pos.get_order().get_orderlines().length == 0 || this.pos.get_order().get_total_with_tax() <=0){
this.gui.show_popup('pos_quot_result',{
'body': _t('Invalid Order : Please Add Some Order Lines'),
});
}
},
});
screens.define_action_button({
'name': 'pos_quotation_order',
'widget': QuotationButton,
});
});

144
pos_quotation_order/static/src/xml/pos_quotation.xml

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<template id="template" xml:space="preserve">
<t t-name="QuotationButton">
<!--<div class="row">-->
<!--<button class="quotation_button">-->
<!--<i class="fa fa-shopping-cart pos-quot-cart"/>-->
<!--<p>Create Quotation</p>-->
<!--</button>-->
<!--</div>-->
<span class="control-button quotation_button">
<i class="fa fa-shopping-cart"/>
Create Quotation
</span>
</t>
<t t-name="QuotationListButton">
<!--<div class="row">-->
<!--<button class="quotation_list_button">-->
<!--<p>Quotation List</p>-->
<!--</button>-->
<!--</div>-->
<span class="control-button quotation_list_button">
Quotation List
</span>
</t>
<t t-name="QuotationPopupWidget">
<div class="modal-dialog">
<div class="popup popup-quot-order">
<p class="title"><t t-esc=" widget.options.title || 'Create Quotation' " /></p>
<p class="body">
<br/>
<br/>
<input type="text" name="order_date" class="order_date" placeholder="Delivery Date(yyyy/mm/dd)" maxlength="10"/>
<br/>
<br/>
<span class="alert_msg"/>
<br/>
<textarea rows="3" cols="34" name="order_note" class="form-control order_note" placeholder="Enter your notes here..."/>
<br/>
</p>
<div class="footer">
<div class="button confirm">
Create Order
</div>
<div class="button cancel">
Close
</div>
</div>
</div>
</div>
</t>
<t t-name="QuotationResultPopupWidget">
<div class="modal-dialog">
<div class="popup popup_quot_ref">
<p class="body"><t t-esc="widget.options.body || '' "/></p>
<div class="footer">
<div class="button cancel">
OK
</div>
</div>
</div>
</div>
</t>
<t t-name="QuotationListScreenWidget">
<div class="quotation_list-screen screen">
<div class="screen-content">
<section class="top-content">
<span class='button back'>
<i class='fa fa-angle-double-left'></i>
Back
</span>
<span class='searchbox'>
<input placeholder='Search Quotation' />
<span class='search-clear'></span>
</span>
<span class='searchbox'></span>
</section>
<section class="full-content">
<div class='window'>
<section class='subwindow'>
<div class='subwindow-container'>
<div class='subwindow-container-fix touch-scrollable scrollable-y'>
<table class='quotation-list'>
<thead>
<tr>
<th>Quotation Reference</th>
<th>Partner Name</th>
<th>Date</th>
<th>Total Amount</th>
<th></th>
</tr>
</thead>
<tbody class='quotation-list-contents'>
</tbody>
</table>
</div>
</div>
</section>
</div>
</section>
</div>
</div>
</t>
<t t-name="QuotationLine">
<tr class='quotation-line' t-att-data-id='quotation.id'>
<td><t t-esc='quotation.name' /></td>
<td><t t-esc='quotation.partner_id[1]'/></td>
<td><t t-esc='quotation.date_order' /></td>
<td><t t-esc='quotation.amount_total' /></td>
<td><button class="confirm_quotation"><i class='fa fa-angle-double-right'></i>
Confirm
</button></td>
</tr>
</t>
<t t-extend="PosTicket">
<t t-jquery='.receipt-change' t-operation='after'>
<t t-if='order.quotation_ref'>
<br/>
<div class='receipt-quotation'>
<table class='receipt-quotation-ref'>
<tr>
<td class="pos-left-align">
Quotation Ref:
</td>
<td class="pos-right-align">
<t t-esc='order.quotation_ref["name"]' />
</td>
</tr>
</table>
</div>
</t>
</t>
</t>
</template>

122
pos_quotation_order/views/pos_quotation.xml

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="view_pos_pos_quotation_form" model="ir.ui.view">
<field name="name">pos.order.form</field>
<field name="model">pos.order</field>
<field name="inherit_id" ref="point_of_sale.view_pos_pos_form"/>
<field name="arch" type="xml">
<field name="partner_id" position="after">
<field name="quot_ref"/>
</field>
</field>
</record>
<record id="seq_pos_quotation" model="ir.sequence">
<field name="name">POS Quotation</field>
<field name="code">pos.quotation</field>
<field name="prefix">POS/QUOT/</field>
<field name="padding">5</field>
<field name="company_id" eval="False" />
</record>
<record id="view_pos_quotation_form" model="ir.ui.view">
<field name="name">pos.quotation.form</field>
<field name="model">pos.quotation</field>
<field name="arch" type="xml">
<form string="Point of Sale Quotations" create="false">
<header>
<field name="state" widget="statusbar" statusbar_visible="draft,confirmed" />
</header>
<sheet>
<group col="4" colspan="4" name="order_fields">
<field name="name"/>
<field name="date_order"/>
<field name="partner_id" domain="[('customer', '=', True)]" context="{'search_default_customer':1}" />
<field name="date_quotation"/>
<field name="pricelist_id" />
</group>
<notebook colspan="4">
<page string="Products">
<field name="lines" colspan="4" nolabel="1">
<tree string="Order lines" editable="bottom">
<field name="product_id"/>
<field name="qty"/>
<field name="price_unit" widget="monetary"/>
<field name="discount" widget="monetary"/>
<field name="tax_ids_after_fiscal_position" widget="many2many_tags"/>
<field name="tax_ids" invisible="1"/>
<field name="price_subtotal" widget="monetary"/>
<field name="price_subtotal_incl" widget="monetary"/>
</tree>
<form string="Order lines">
<group col="4">
<field name="product_id"/>
<field name="qty"/>
<field name="discount" widget="monetary"/>
<field name="price_unit" widget="monetary"/>
<field name="price_subtotal" invisible="1" widget="monetary"/>
<field name="price_subtotal_incl" invisible="1" widget="monetary"/>
<field name="tax_ids_after_fiscal_position" widget="many2many_tags"/>
<field name="tax_ids" invisible="1"/>
<field name="notice"/>
</group>
</form>
</field>
<group class="oe_subtotal_footer oe_right" colspan="2" name="order_total">
<field name="amount_tax" widget="monetary"/>
<div class="oe_subtotal_footer_separator oe_inline">
<label for="amount_total" />
<button name="button_dummy" string="(update)" class="oe_edit_only oe_link"/>
</div>
<field name="amount_total" nolabel="1" class="oe_subtotal_footer_separator" widget="monetary"/>
</group>
<div class="oe_clear"/>
</page>
<page string="Notes" >
<field name="note"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="view_pos_quotation_tree" model="ir.ui.view">
<field name="name">pos.quotation.tree</field>
<field name="model">pos.quotation</field>
<field name="arch" type="xml">
<tree string="Point of Sale Quotations" create="false" decoration-danger="state == 'draft'" decoration-success="state == 'confirmed'" >
<field name="name"/>
<field name="partner_id"/>
<field name="amount_total"/>
<field name="state"/>
</tree>
</field>
</record>
<record id="action_pos_quotation_form" model="ir.actions.act_window">
<field name="name">Quotations</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.quotation</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" eval="False"/>
<field name="domain">[]</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a new order.
</p><p>
Use this menu to browse previous quotations. To record new
quotations, you may use the menu <i>Your Session</i> for
the touchscreen interface.
</p>
</field>
</record>
<menuitem id="menu_pos_quotation"
name="Quotation"
parent="point_of_sale.menu_point_of_sale"
action="action_pos_quotation_form"
sequence="0"
groups="point_of_sale.group_pos_manager,point_of_sale.group_pos_user"/>
</odoo>

12
pos_quotation_order/views/quotation_templates.xml

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<template id="pos_sale_order_template" inherit_id="point_of_sale.assets">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/pos_quotation_order/static/src/css/pos_quotation.css" />
<script type="text/javascript" src="/pos_quotation_order/static/src/js/models.js"/>
<script type="text/javascript" src="/pos_quotation_order/static/src/js/pos_quotation.js"/>
</xpath>
</template>
</data>
</odoo>
Loading…
Cancel
Save