[ADD] website_portal_purchase: purchase orders available in portal

PO are now available in customer portal. It follows the same
convention as the rest of exposed documents (list, pager, form,
filters, ...)

To insert it after the SO menu item, some ordering was required.
Also, rename 'Orders' into 'Sales Orders' to avoid confusion
with 'Purchase Orders'.
This commit is contained in:
fwi-odoo
2017-03-23 14:50:40 +01:00
committed by Jérome Maes
parent 936b215adf
commit 25a067b58d
11 changed files with 390 additions and 4 deletions
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import controllers
import models
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Website Portal for Purchases',
'category': 'Website',
'summary': 'Add your purchase orders in the frontend portal',
'version': '1.0',
'description': """
Display purchase orders on your website. Customers will be able to access their purchase orders in their own portal, see their state and communicate with salesmen.
""",
'depends': [
'purchase',
'website_portal',
],
'data': [
'views/website_portal_purchase_templates.xml',
'security/ir.model.access.csv',
'security/website_portal_purchase_security.xml',
],
'installable': True,
}
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import website_portal
@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import OrderedDict
from odoo import http
from odoo.exceptions import AccessError
from odoo.http import request
from odoo.tools.translate import _
from odoo.addons.website_portal.controllers.main import website_account, get_records_pager
class WebsitePortal(website_account):
def _prepare_portal_layout_values(self):
values = super(WebsitePortal, self)._prepare_portal_layout_values()
partner = request.env.user.partner_id
values['purchase_count'] = request.env['purchase.order'].search_count([
'|',
('message_partner_ids', 'child_of', [partner.commercial_partner_id.id]),
('partner_id', 'child_of', [partner.commercial_partner_id.id]),
('state', 'in', ['purchase', 'done', 'cancel'])
])
return values
@http.route(['/my/purchase', '/my/purchase/page/<int:page>'], type='http', auth="user", website=True)
def portal_my_purchase_orders(self, page=1, date_begin=None, date_end=None, sortby=None, filterby=None, **kw):
values = self._prepare_portal_layout_values()
partner = request.env.user.partner_id
PurchaseOrder = request.env['purchase.order']
domain = [
'|',
('message_partner_ids', 'child_of', [partner.commercial_partner_id.id]),
('partner_id', 'child_of', [partner.commercial_partner_id.id]),
]
archive_groups = self._get_archive_groups('purchase.order', domain)
if date_begin and date_end:
domain += [('create_date', '>', date_begin), ('create_date', '<=', date_end)]
searchbar_sortings = {
'date': {'label': _('Newest'), 'order': 'create_date desc, id desc'},
'name': {'label': _('Name'), 'order': 'name asc, id asc'},
'amount_total': {'label': _('Total'), 'order': 'amount_total desc, id desc'},
}
# default sort by value
if not sortby:
sortby = 'date'
order = searchbar_sortings[sortby]['order']
searchbar_filters = {
'all': {'label': _('All'), 'domain': [('state', 'in', ['purchase', 'done', 'cancel'])]},
'purchase': {'label': _('Purchase Order'), 'domain': [('state', '=', 'purchase')]},
'cancel': {'label': _('Cancelled'), 'domain': [('state', '=', 'cancel')]},
'done': {'label': _('Locked'), 'domain': [('state', '=', 'done')]},
}
# default filter by value
if not filterby:
filterby = 'all'
domain += searchbar_filters[filterby]['domain']
# count for pager
purchase_count = PurchaseOrder.search_count(domain)
# make pager
pager = request.website.pager(
url="/my/purchase",
url_args={'date_begin': date_begin, 'date_end': date_end},
total=purchase_count,
page=page,
step=self._items_per_page
)
# search the purchase orders to display, according to the pager data
orders = PurchaseOrder.search(
domain,
order=order,
limit=self._items_per_page,
offset=pager['offset']
)
request.session['my_purchases_history'] = orders.ids[:100]
values.update({
'date': date_begin,
'orders': orders,
'page_name': 'purchase',
'pager': pager,
'archive_groups': archive_groups,
'searchbar_sortings': searchbar_sortings,
'sortby': sortby,
'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
'filterby': filterby,
'default_url': '/my/purchase',
})
return request.render("website_portal_purchase.portal_my_purchase_orders", values)
@http.route(['/my/purchase/<int:order_id>'], type='http', auth="user", website=True)
def portal_my_purchase_order(self, order_id=None, **kw):
order = request.env['purchase.order'].browse(order_id)
try:
order.check_access_rights('read')
order.check_access_rule('read')
except AccessError:
return request.render("website.403")
history = request.session.get('my_purchases_history', [])
values = {
'order': order.sudo(),
}
values.update(get_records_pager(history, order))
return request.render("website_portal_purchase.portal_my_purchase_order", values)
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import purchase
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class PurchaseOrder(models.Model):
_inherit = 'purchase.order'
website_url = fields.Char('Website URL', compute='_website_url', help='The full URL to access the document through the website.')
def _website_url(self):
for order in self:
order.website_url = '/my/purchase/%s' % (order.id)
class PurchaseOrderLine(models.Model):
_inherit = 'purchase.order.line'
# Non-stored related field to allow portal user to see the image of the product he has ordered
product_image = fields.Binary('Product Image', related="product_id.image")
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_purchase_order_portal,purchase.order.portal,purchase.model_purchase_order,base.group_portal,1,0,0,0
access_purchase_order_line_portal,purchase.order.line.portal,purchase.model_purchase_order_line,base.group_portal,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_purchase_order_portal purchase.order.portal purchase.model_purchase_order base.group_portal 1 0 0 0
3 access_purchase_order_line_portal purchase.order.line.portal purchase.model_purchase_order_line base.group_portal 1 0 0 0
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="portal_purchase_order_user_rule" model="ir.rule">
<field name="name">Portal Purchase Orders</field>
<field name="model_id" ref="purchase.model_purchase_order"/>
<field name="domain_force">[('message_partner_ids','child_of',[user.commercial_partner_id.id])]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
<field name="perm_unlink" eval="1"/>
<field name="perm_write" eval="1"/>
<field name="perm_read" eval="1"/>
<field name="perm_create" eval="0"/>
</record>
<record id="portal_purchase_order_line_rule" model="ir.rule">
<field name="name">Portal Purhcase Orders Line</field>
<field name="model_id" ref="purchase.model_purchase_order_line"/>
<field name="domain_force">[('order_id.message_partner_ids','child_of',[user.commercial_partner_id.id])]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
</data>
</odoo>
@@ -0,0 +1,6 @@
/* ---- My Purchase Orders page ---- */
.purchases_vertical_align {
display: flex;
align-items: center;
}
@@ -0,0 +1,175 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="portal_my_home_menu_purchase" name="Portal layout : purchase menu entries" inherit_id="website_portal.portal_layout" priority="25">
<xpath expr="//ul[contains(@class,'o_portal_submenu')]" position="inside">
<t t-if="purchase_count">
<li t-att-class="page_name == 'purchase' and 'active'">
<a href="/my/purchase">Purchase Orders</a>
</li>
</t>
</xpath>
</template>
<template id="portal_my_home_purchase" name="Portal My Home : purchase entry" inherit_id="website_portal.portal_my_home" priority="25">
<xpath expr="//div[contains(@class,'o_my_home_content')]" position="inside">
<h3 t-if="purchase_count" class="page-header">
<a href="/my/purchase">Your Purchase Orders
<small class="ml8">
<span class='badge'><t t-esc="purchase_count"/></span>
</small>
</a>
</h3>
</xpath>
</template>
<template id="portal_my_purchase_orders" name="Portal: My Purchase Orders">
<t t-call="website_portal.portal_layout">
<h3 class="page-header">
Your Purchase Orders
<t t-call="website_portal.portal_searchbar"/>
</h3>
<table class="table table-hover o_my_status_table">
<thead>
<tr class="active">
<th>Purchase Orders #</th>
<th>Order Date</th>
<th></th>
<th>Total</th>
</tr>
</thead>
<t t-foreach="orders" t-as="order">
<tr>
<td>
<a t-attf-href="/my/purchase/#{order.id}?#{keep_query()}"><t t-esc="order.name"/></a>
</td>
<td><span t-field="order.date_order"/></td>
<td>
<t t-if="order.state == 'cancel'">
<span class="label label-default"><i class="fa fa-fw fa-remove"/> Cancelled</span>
</t>
</td>
<td><span t-field="order.amount_total" t-options='{"widget": "monetary", "display_currency": order.currency_id}'/>
</td>
</tr>
</t>
</table>
<div t-if="pager" class="o_portal_pager text-center">
<t t-call="website.pager"/>
</div>
</t>
</template>
<template id="portal_my_purchase_order" name="Portal: My Purchase Order">
<t t-call="website.layout">
<div id="wrap">
<div class="container">
<div class="oe_structure">
<div class="row">
<div class="col-sm-4">
<ol class="breadcrumb mt8">
<li><a href="/my/home"><i class="fa fa-home"/></a></li>
<li>
<a t-attf-href="/my/purchase?{{ keep_query()}}">
My Purchase Orders
</a>
</li>
<li>
<t t-esc="order.name"/>
</li>
</ol>
</div>
</div>
</div>
<div id="optional_placeholder"></div>
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<div class="row">
<div class="col-md-12">
<h4>
<t t-call='website_portal.record_pager'/>
Purchase Order
<span t-esc="order.name"/>
</h4>
</div>
</div>
</div>
<div class="panel-body">
<div class="mb8">
<strong>Date:</strong> <span t-field="order.date_order" t-options='{"widget": "date"}'/>
</div>
<div class="row">
<div class="col-md-6">
<strong>Product</strong>
</div>
<div class="col-md-2 text-right">
<strong>Unit Price</strong>
</div>
<div class="col-md-2 text-right">
<strong>Quantity</strong>
</div>
<div class="col-md-2 text-right">
<strong>Subtotal</strong>
</div>
</div>
<t t-foreach="order.order_line" t-as="ol">
<div class="row purchases_vertical_align">
<div class="col-md-1 text-center">
<img t-att-src="'/web/image/purchase.order.line/%s/product_image/48x48' % ol.id"/>
</div>
<div id='product_name' class="col-md-5">
<span t-esc="ol.product_id.name"/>
</div>
<div class="col-md-2 text-right">
<span t-field="ol.price_unit" t-options='{"widget": "monetary", "display_currency": order.currency_id}'/>
</div>
<div class="col-md-2 text-right">
<span t-esc="ol.product_qty"/>
</div>
<div class="col-md-2 text-right">
<span t-field="ol.price_subtotal" t-options='{"widget": "monetary", "display_currency": order.currency_id}'/>
</div>
</div>
</t>
<hr/>
<div class="row">
<div class="col-md-12 text-right">
<div class="row">
<div class="col-md-10 text-right">
Untaxed Amount:
</div>
<div class="col-md-2 text-right">
<span t-field="order.amount_untaxed" t-options='{"widget": "monetary", "display_currency": order.currency_id}'/>
</div>
</div>
<div class="row">
<div class="col-md-10 text-right">
Taxes:
</div>
<div class="col-md-2 text-right">
<span t-field="order.amount_tax" t-options='{"widget": "monetary", "display_currency": order.currency_id}'/>
</div>
</div>
<div class="row">
<div class="col-md-10 text-right">
<strong>Total:</strong>
</div>
<div class="col-md-2 text-right">
<strong><span t-field="order.amount_total" t-options='{"widget": "monetary", "display_currency": order.currency_id}'/></strong>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="oe_structure mb32"/>
</div>
</div>
</t>
</template>
</odoo>
@@ -12,8 +12,13 @@
<a href="/my/quotes">Quotes</a>
</li>
<li t-att-class="page_name == 'order' and 'active'">
<a href="/my/orders">Orders</a>
<a href="/my/orders">Sales Orders</a>
</li>
</xpath>
</template>
<template id="portal_my_home_menu_invoice" name="Portal layout : invoice menu entries" inherit_id="website_portal.portal_layout" priority="30">
<xpath expr="//ul[contains(@class,'o_portal_submenu')]" position="inside">
<li t-if="invoice_count" t-att-class="page_name == 'invoice' and 'active'">
<a href="/my/invoices">Invoices</a>
</li>
@@ -30,7 +35,7 @@
</a>
</h3>
<h3 class="page-header">
<a href="/my/orders">Your Orders
<a href="/my/orders">Your Sales Orders
<small class="ml8">
<t t-if="order_count">
<span class='badge'><t t-esc="order_count"/></span>
@@ -41,6 +46,11 @@
</small>
</a>
</h3>
</xpath>
</template>
<template id="portal_my_home_invoice" name="Portal My Home : invoice entries" inherit_id="website_portal.portal_my_home" priority="30">
<xpath expr="//div[contains(@class,'o_my_home_content')]" position="inside">
<h3 t-if="invoice_count" class="page-header">
<a href="/my/invoices">Your Invoices and Payments
<small class="ml8">
@@ -101,7 +111,7 @@
<template id="portal_my_orders" name="My Sales Orders">
<t t-call="website_portal.portal_layout">
<div class="page-header">
<span class="h3">Your Orders</span>
<span class="h3">Your Sales Orders</span>
<t t-call="website_portal.portal_searchbar"/>
</div>
<t t-if="not orders">
@@ -219,7 +229,7 @@
My Quotations
</t>
<t t-if="order.state != 'sent'">
My Orders
My Sales Orders
</t>
</a>
</li>