Skip to content
Snippets Groups Projects
Commit cec43b93 authored by Antony Lesuisse's avatar Antony Lesuisse
Browse files

[ADD] website portal and digital download

Add an orders section to the e-commerce to display sales orders, delivery
orders and invoices. Add support for digital products.
parents 0802ebdb 8a1b8e88
No related branches found
No related tags found
No related merge requests found
Showing
with 664 additions and 250 deletions
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_stock_picking,stock.picking,stock.model_stock_picking,base.group_portal,1,0,0,0
access_stock_picking_type_portal,stock.picking.type portal,stock.model_stock_picking_type,base.group_portal,1,0,0,0
access_stock_pack_operation_portal,stock.pack.operation portal,stock.model_stock_pack_operation,base.group_portal,1,0,0,0
access_stock_move,stock.move,stock.model_stock_move,base.group_portal,1,0,0,0
access_stock_warehouse_orderpoint,stock.warehouse.orderpoint,stock.model_stock_warehouse_orderpoint,base.group_portal,1,0,0,0
......@@ -3,10 +3,17 @@
<data noupdate="1">
<!-- Stock Portal Access Rules -->
<record id="portal_stock_picking_user_rule" model="ir.rule">
<field name="name">Portal Personal Pickings</field>
<record id="portal_stock_picking_portal_user_rule" model="ir.rule">
<field name="name">Portal Follower Pickings</field>
<field name="model_id" ref="stock.model_stock_picking"/>
<field name="domain_force">[('message_follower_ids','in',[user.partner_id.id])]</field>
<field name="domain_force">['|', ('message_follower_ids','in',[user.partner_id.id]), ('partner_id.id','=',user.partner_id.id)]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
<record id="portal_stock_pack_operation_portal_user_rule" model="ir.rule">
<field name="name">Portal Follower Pack Operations</field>
<field name="model_id" ref="stock.model_stock_pack_operation"/>
<field name="domain_force">['|', ('picking_id.message_follower_ids','in',[user.partner_id.id]), ('picking_id.partner_id.id','=',user.partner_id.id)]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
</data>
......
import controllers
import models
import res_config
......@@ -18,6 +18,7 @@ OpenERP E-Commerce
'views/payment.xml',
'views/sale_order.xml',
'views/snippets.xml',
'res_config_view.xml',
'security/ir.model.access.csv',
'security/website_sale.xml',
],
......
......@@ -336,6 +336,33 @@ class website_sale(http.Controller):
})
return value
@http.route([
'/shop/orders',
'/shop/orders/page/<int:page>',
], type='http', auth="user", website=True)
def orders_followup(self, page=1, by = 5, **post):
partner = request.env['res.users'].browse(request.uid).partner_id
orders = request.env['sale.order'].sudo().search([('partner_id', '=', partner.id), ('state', 'not in', ['draft', 'cancel'])])
nbr_pages = max((len(orders) / by) + (1 if len(orders) % by > 0 else 0), 1)
page = min(page, nbr_pages)
pager = request.website.pager(
url='/shop/orders', total=nbr_pages, page=page, step=1,
scope=by, url_args=post
)
orders = orders[by*(page-1):by*(page-1)+by]
order_invoice_lines = {}
for o in orders:
invoiced_lines = request.env['account.invoice.line'].sudo().search([('invoice_id', 'in', o.invoice_ids.ids)])
order_invoice_lines[o.id] = {il.product_id.id: il.invoice_id for il in invoiced_lines}
return request.website.render("website_sale.orders_followup", {
'orders': orders,
'order_invoice_lines': order_invoice_lines,
'pager': pager,
})
#------------------------------------------------------
# Checkout
#------------------------------------------------------
......@@ -925,4 +952,4 @@ class website_sale(http.Controller):
'currency': order.currency_id.name
}
ret['lines'] = self.order_lines_2_google_api(order.order_line)
return ret
return ret
\ No newline at end of file
# -*- encoding: utf-8 -*-
from openerp import models, fields
from openerp.addons.sale.res_config import sale_configuration
class website_sale_configuration(models.TransientModel):
_inherit = 'sale.config.settings'
module_website_sale_digital = fields.Boolean('Sell digital products',
help="Allow you to set mark a product as a digital product, allowing customers that have purchased the product to download its attachments. This installs the module website_sale_digital.")
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_website_sales_config" model="ir.ui.view">
<field name="name">website_sale settings</field>
<field name="model">sale.config.settings</field>
<field name="inherit_id" ref="sale.view_sales_config"/>
<field name="arch" type="xml">
<div name="customer feature" position="before">
<separator string="Products Management"/>
<group>
<label for="id" string="Product Types"/>
<div>
<div name="group_invoice_so_lines">
<field name="module_website_sale_digital" class="oe_inline"/>
<label for="module_website_sale_digital"/>
</div>
</div>
</group>
</div>
</field>
</record>
</odoo>
@charset "utf-8";
/* ---- Default Styles ---- */
.oe_product {
border: 1px solid rgba(100, 100, 100, 0.2);
}
border: 1px solid rgba(100, 100, 100, 0.2); }
.products_pager {
text-align: center;
}
.products_pager > div {
margin: 0 auto;
display: inline-block;
}
.products_pager > div > * {
vertical-align: middle;
float: none;
padding: 0;
}
.products_pager > div > ul {
padding-left: 10px;
}
.products_pager > ul {
margin-left: auto;
margin-right: auto;
}
text-align: center; }
.products_pager > div {
margin: 0 auto;
display: inline-block; }
.products_pager > div > * {
vertical-align: middle;
float: none;
padding: 0; }
.products_pager > div > ul {
padding-left: 10px; }
.products_pager > ul {
margin-left: auto;
margin-right: auto; }
.oe_website_sale ul ul {
margin-left: 1.5em;
}
margin-left: 1.5em; }
/* ---- Default Styles ---- */
.oe_product {
position: relative;
}
.oe_product .oe_product_image {
position: absolute;
left: 15px;
right: 15px;
top: 15px;
bottom: 55px;
text-align: center;
}
.oe_product .oe_product_image img {
max-width: 100%;
max-height: 100%;
margin: auto;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: 4;
}
.oe_product section {
position: absolute;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
padding: 0 15px 24px 10px;
min-height: 56px;
border-top: 1px solid rgba(255, 255, 255, 0.2);
background: rgba(255, 255, 255, 0.75);
z-index: 5;
}
.oe_product .product_price {
padding: 5px 0;
position: absolute;
bottom: 0;
}
.oe_product .oe_product_cart {
position: relative;
width: 100%;
height: 100%;
}
.oe_product .oe_subdescription {
font-size: 0.8em;
overflow: hidden;
margin-bottom: 10px;
}
position: relative; }
.oe_product .oe_product_image {
position: absolute;
left: 15px;
right: 15px;
top: 15px;
bottom: 55px;
text-align: center; }
.oe_product .oe_product_image img {
max-width: 100%;
max-height: 100%;
margin: auto;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: 4; }
.oe_product section {
position: absolute;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
padding: 0 15px 24px 10px;
min-height: 56px;
border-top: 1px solid rgba(255, 255, 255, 0.2);
background: rgba(255, 255, 255, 0.75);
z-index: 5; }
.oe_product .product_price {
padding: 5px 0;
position: absolute;
bottom: 0; }
.oe_product .oe_product_cart {
position: relative;
width: 100%;
height: 100%; }
.oe_product .oe_subdescription {
font-size: 0.8em;
overflow: hidden;
margin-bottom: 10px; }
.oe_mycart .input-group-addon {
padding-left: 6px;
padding-right: 6px;
}
padding-right: 6px; }
/* Products Options */
.oe_product.oe_image_full .oe_product_image {
left: 0;
right: 0;
top: 0;
bottom: 0;
}
bottom: 0; }
/* Ribbon for promotionso on products */
.oe_product.oe_ribbon_promo .ribbon-wrapper {
display: block;
}
display: block; }
.oe_product .ribbon-wrapper {
display: none;
width: 85px;
......@@ -104,8 +86,7 @@
overflow: hidden;
position: absolute;
top: 0;
right: 0;
}
right: 0; }
.oe_product .ribbon {
font: bold 15px Sans-Serif;
color: white;
......@@ -119,141 +100,99 @@
left: -5px;
top: 15px;
width: 120px;
cursor: default;
}
cursor: default; }
/* ---- Product Sizes ---- */
.col-md-12 .oe-height-1 {
height: 75px;
}
height: 75px; }
.col-md-12 .oe-height-2 {
height: 150px;
}
height: 150px; }
.col-md-12 .oe-height-4 {
height: 300px;
}
height: 300px; }
.col-md-12 .oe-height-6 {
height: 450px;
}
height: 450px; }
.col-md-12 .oe-height-8 {
height: 600px;
}
height: 600px; }
.col-md-9 .oe-height-1 {
height: 70px;
}
height: 70px; }
.col-md-9 .oe-height-2 {
height: 140px;
}
height: 140px; }
.col-md-9 .oe-height-4 {
height: 280px;
}
height: 280px; }
.col-md-9 .oe-height-6 {
height: 420px;
}
height: 420px; }
.col-md-9 .oe-height-8 {
height: 560px;
}
height: 560px; }
@media (max-width: 768px) {
#products_grid table, #products_grid tbody, #products_grid tr, #products_grid td {
float: left;
width: 100%;
display: inline-block;
}
display: inline-block; }
#products_grid .oe_product {
float: left;
width: 100%;
height: 300px;
display: inline-block;
}
display: inline-block; }
.products_pager .pagination {
margin: 5px auto !important;
}
}
margin: 5px auto !important; } }
@media (max-width: 400px) {
#products_grid .oe_product {
height: 270px;
}
#products_grid .oe_product section {
background: rgba(100, 100, 100, 0.2);
}
#products_grid .oe_product .oe_product_image {
top: 0;
bottom: 65px;
}
height: 270px; }
#products_grid .oe_product section {
background: rgba(100, 100, 100, 0.2); }
#products_grid .oe_product .oe_product_image {
top: 0;
bottom: 65px; }
#products_grid .oe_product.oe_image_full .oe_product_image {
bottom: 50px;
}
}
bottom: 50px; } }
@media (min-width: 992px) {
.col-md-12 .oe-height-1 {
height: 95px;
}
height: 95px; }
.col-md-12 .oe-height-2 {
height: 190px;
}
height: 190px; }
.col-md-12 .oe-height-4 {
height: 380px;
}
height: 380px; }
.col-md-12 .oe-height-6 {
height: 570px;
}
height: 570px; }
.col-md-12 .oe-height-8 {
height: 760px;
}
height: 760px; }
.col-md-9 .oe-height-1 {
height: 72px;
}
height: 72px; }
.col-md-9 .oe-height-2 {
height: 144px;
}
height: 144px; }
.col-md-9 .oe-height-4 {
height: 288px;
}
height: 288px; }
.col-md-9 .oe-height-6 {
height: 432px;
}
height: 432px; }
.col-md-9 .oe-height-8 {
height: 576px;
}
}
height: 576px; } }
@media (min-width: 1200px) {
.col-md-12 .oe-height-1 {
height: 125px;
}
height: 125px; }
.col-md-12 .oe-height-2 {
height: 250px;
}
height: 250px; }
.col-md-12 .oe-height-4 {
height: 500px;
}
height: 500px; }
.col-md-12 .oe-height-6 {
height: 750px;
}
height: 750px; }
.col-md-12 .oe-height-8 {
height: 1000px;
}
height: 1000px; }
.col-md-9 .oe-height-1 {
height: 95px;
}
height: 95px; }
.col-md-9 .oe-height-2 {
height: 190px;
}
height: 190px; }
.col-md-9 .oe-height-4 {
height: 380px;
}
height: 380px; }
.col-md-9 .oe-height-6 {
height: 570px;
}
height: 570px; }
.col-md-9 .oe-height-8 {
height: 760px;
}
}
height: 760px; } }
/* ---- Product list style ---- */
@media (min-width: 400px) {
.oe_product.oe_list {
......@@ -262,115 +201,98 @@
width: 100%;
min-height: 100px;
position: relative;
padding-bottom: 5px;
}
.oe_product.oe_list .oe_product_image {
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 170px;
}
.oe_product.oe_list section {
position: relative;
border: 0;
top: 0;
bottom: auto;
left: 180px;
background: transparent;
}
}
padding-bottom: 5px; }
.oe_product.oe_list .oe_product_image {
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 170px; }
.oe_product.oe_list section {
position: relative;
border: 0;
top: 0;
bottom: auto;
left: 180px;
background: transparent; } }
.oe_website_sale .row .row .col-md-12 {
float: none;
}
float: none; }
/* product detail */
.css_attribute_color {
display: inline-block;
border: 1px solid #999999;
text-align: center;
}
.css_attribute_color input {
margin: 8px;
height: 13px;
opacity: 0;
}
.css_attribute_color.active {
border: 3px ridge #66ee66;
}
.css_attribute_color.active input {
margin: 6px;
}
text-align: center; }
.css_attribute_color input {
margin: 8px;
height: 13px;
opacity: 0; }
.css_attribute_color.active {
border: 3px ridge #66ee66; }
.css_attribute_color.active input {
margin: 6px; }
.css_not_available_msg {
display: none;
}
display: none; }
.css_not_available.js_product > *:nth-child(4) > * {
display: none;
}
display: none; }
.css_not_available.js_product .product_price, .css_not_available.js_product .css_quantity {
display: none;
}
display: none; }
.css_not_available.js_product .css_not_available_msg {
display: block;
}
display: block; }
option.css_not_available {
color: #cccccc;
}
color: #ccc; }
label.css_not_available {
opacity: 0.6;
}
opacity: 0.6; }
label.css_attribute_color.css_not_available {
opacity: 1;
background-image: url("/website_sale/static/src/img/redcross.png");
background-size: cover;
}
background-size: cover; }
.product_detail_img {
margin-left: auto;
margin-right: auto;
display: block;
max-height: 500px;
}
max-height: 500px; }
.oe_default_price {
display: none;
}
display: none; }
.discount .oe_default_price {
display: block;
}
display: block; }
.oe_website_sale input.js_quantity {
min-width: 48px;
text-align: center;
}
text-align: center; }
/* ---- Publish managment and options ---- */
.oe_overlay_options .dropdown ul[name="size"] table {
margin-left: 20px;
}
margin-left: 20px; }
.oe_overlay_options .dropdown ul[name="size"] td {
margin: 0;
padding: 0;
width: 20px;
height: 20px;
border: 1px #dddddd solid;
cursor: pointer;
}
.oe_overlay_options .dropdown ul[name="size"] td.selected {
background-color: #b1d4f1;
}
cursor: pointer; }
.oe_overlay_options .dropdown ul[name="size"] td.selected {
background-color: #B1D4F1; }
.oe_overlay_options .dropdown ul[name="size"] table.oe_hover td.selected {
background-color: transparent;
}
background-color: transparent; }
.oe_overlay_options .dropdown ul[name="size"] table.oe_hover td.select {
background-color: #b1d4f1;
}
background-color: #B1D4F1; }
/* ---- My Orders page ---- */
.orders_vertical_align {
display: flex;
align-items: center; }
.orders_label_text_align {
vertical-align: 15%; }
/* Wizard */
ul.wizard {
......@@ -382,8 +304,7 @@ ul.wizard {
border-radius: 4px;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.065);
-moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.065);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.065);
}
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.065); }
ul.wizard li {
border: 1px solid #d4d4d4;
......@@ -393,8 +314,7 @@ ul.wizard li {
padding: 0 10px 0 20px;
margin: 0;
line-height: 38px;
background: #fbfbfb;
}
background: #fbfbfb; }
ul.wizard li .chevron {
position: absolute;
......@@ -404,8 +324,7 @@ ul.wizard li .chevron {
display: block;
border: 20px solid transparent;
border-right: 0;
border-left: 10px solid #d4d4d4;
}
border-left: 10px solid #d4d4d4; }
ul.wizard li .chevron:before {
position: absolute;
......@@ -415,26 +334,22 @@ ul.wizard li .chevron:before {
border: 20px solid transparent;
border-right: 0;
border-left: 10px solid #fbfbfb;
content: "";
}
content: ""; }
ul.wizard li.text-success {
background: #f3f4f5;
}
background: #f3f4f5; }
ul.wizard li.text-success .chevron:before {
border-left: 10px solid whitesmoke;
}
border-left: 10px solid #f5f5f5; }
ul.wizard li.text-primary {
background: #f1f6fc;
}
background: #f1f6fc; }
ul.wizard li.text-primary .chevron:before {
border-left: 10px solid #f1f6fc;
}
border-left: 10px solid #f1f6fc; }
ul.wizard li:first-child {
padding-left: 15px;
border-radius: 4px 0 0 4px;
}
border-radius: 4px 0 0 4px; }
/*# sourceMappingURL=website_sale.css.map */
......@@ -315,6 +315,17 @@ label.css_attribute_color.css_not_available
table.oe_hover td.select
background-color: #B1D4F1
/* ---- My Orders page ---- */
.orders_vertical_align
display: flex
align-items: center
.orders_label_text_align
vertical-align: 15%
/* Wizard */
......
......@@ -1212,5 +1212,216 @@
</xpath>
</template>
<template id="orders_followup" name="Odoo - Follow your Orders">
<t t-call="website.layout">
<div id="wrap">
<div class="oe_structure">
<h1 class="text-center">
Odoo Orders History
</h1>
<h2 class="text-center text-muted">
Follow your Orders
</h2>
</div>
<div class="container oe_website_sale">
<t t-if="not orders">
<h4 class="text-muted text-center mt64 mb64">
You have yet to order anything, why not <a href="/shop/">start now</a> ?
</h4>
</t>
<t t-call="website_sale.orders_followup_navbar"/>
<t t-foreach="orders" t-as="o">
<div class='row'>
<div class="panel panel-default">
<div class="panel-heading">
<div class="row">
<div class="col-md-12">
<h4>
<t t-if="o.state == 'sent'">
Quotation N°
</t>
<t t-if="o.state != 'sent'">
Order N°
</t>
<span t-field="o.name"/>
</h4>
</div>
</div>
</div>
<div class="panel-body">
<!-- <hr/> -->
<div class="mb8">
<strong>Date:</strong> <span t-field="o.create_date" t-field-options='{"widget": "date"}'/>
</div>
<div class='row'>
<div class="col-md-6">
<div>
<strong>Invoicing Address</strong>
</div>
<div>
<address t-field="o.partner_invoice_id" t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}'/>
</div>
<t t-set="invoices" t-value="[i for i in o.invoice_ids if i.state not in ['draft', 'cancel']]"/>
<t t-if="invoices">
<div>
<strong>Invoices</strong>
</div>
<div>
<t t-foreach="invoices" t-as="i">
<t t-set="report_url" t-value="'/report/pdf/account.report_invoice/%s' % i.id"/>
<div>
<a t-att-href="report_url"><span class="fa fa-download"/></a>
<a t-att-href="report_url"><span t-field="i.number"/></a>
<span class="text-muted" t-field="i.date_invoice"/>
<t t-if="i.state == 'paid'">
<span class="label label-success orders_label_text_align"><i class="fa fa-fw fa-check"/> Paid</span>
</t>
<t t-if="i.state != 'paid'">
<span class="label label-info orders_label_text_align"><i class="fa fa-fw fa-clock-o"/> Waiting</span>
</t>
</div>
</t>
</div>
</t>
</div>
<div id="shipping_address" class="col-md-6">
<div>
<strong>Shipping Address</strong>
</div>
<div>
<address t-field="o.partner_shipping_id" t-field-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}'/>
</div>
</div>
</div>
<hr/>
<div id="product_lines">
<!-- Product lines come here -->
</div>
<hr/>
<div class="row">
<div class="col-md-6">
<div>
<strong>Contact</strong>
</div>
<div t-field="o.user_id.partner_id" t-field-options='{"widget": "contact", "fields": ["email", "phone"]}'/>
</div>
<div class="col-md-6">
<div class="row">
<div class="col-md-10 text-right">
Untaxed Amount:
</div>
<div class="col-md-2 text-right">
<span t-field="o.amount_untaxed" t-field-options='{"widget": "monetary", "display_currency": "o.company_id.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="o.amount_tax" t-field-options='{"widget": "monetary", "display_currency": "o.company_id.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="o.amount_total" t-field-options='{"widget": "monetary", "display_currency": "o.company_id.currency_id"}'/></strong>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</t>
<t t-if="len(orders) >= 2">
<t t-call="website_sale.orders_followup_navbar"/>
</t>
</div>
<div class="oe_structure mb32"/>
</div>
</t>
</template>
<template id="sales_backend_access" inherit_id='website_sale.orders_followup' customize_show="True" name="Backend Access">
<xpath expr="//div[@class='container oe_website_sale']" position="before">
<h4 class="text-muted text-center mt64 mb64">
If you wish to access the complete application to manage your orders, you can follow
<a href="/web#action=portal_sale.action_orders_portal">this link</a>.
</h4>
</xpath>
</template>
<template id="order_invoice" name="Display Products similarly to Invoice form view" inherit_id="website_sale.orders_followup">
<xpath expr="//div[@id='product_lines']" position="inside">
<div class="row">
<div class="col-md-8 col-md-offset-1">
<strong>Product</strong>
</div>
<div class="col-md-1 text-right">
<strong>Unit Price</strong>
</div>
<div class="col-md-1 text-right">
<strong>Quantity</strong>
</div>
<div class="col-md-1 text-right">
<strong>Subtotal</strong>
</div>
</div>
<t t-foreach="o.order_line" t-as="ol">
<div class="row orders_vertical_align">
<div class="col-md-1 text-center">
<a t-att-href="'/shop/product/%s' % str(ol.product_id.product_tmpl_id.id)">
<img t-att-src="'/website/image/product.product/%s/image_small/48x48' % ol.product_id.id"/>
</a>
</div>
<div id='product_name' class="col-md-8">
<a t-att-href="'/shop/product/%s' % str(ol.product_id.product_tmpl_id.id)"><span t-field="ol.product_id.name"/></a>
</div>
<div class="col-md-1 text-right">
<span t-field="ol.price_unit" t-field-options='{"widget": "monetary", "display_currency": "o.company_id.currency_id"}'/>
</div>
<div class="col-md-1 text-right">
<t t-if="ol._name == 'sale.order.line'">
<span t-field="ol.product_uom_qty"/>
</t>
<t t-if="ol._name == 'account.invoice.line'">
<span t-field="ol.quantity"/>
</t>
</div>
<div class="col-md-1 text-right">
<span t-field="ol.price_subtotal" t-field-options='{"widget": "monetary", "display_currency": "o.company_id.currency_id"}'/>
</div>
</div>
</t>
</xpath>
</template>
<template id="orders_followup_navbar" name="Navbar for My Orders page">
<div class="row">
<div class="col-md-12">
<span class="pull-right">
<t t-call="website.pager"/>
</span>
</div>
</div>
</template>
<template id="orders_followup_link" name="Orders Followup Link from User Account" inherit_id="website.layout">
<xpath expr="//li[@class='dropdown']/ul/li[contains(.,'Logout')]" position="before">
<li><a href="/shop/orders" role="menuitem">My Orders</a></li>
</xpath>
</template>
</data>
</openerp>
# -*- encoding: utf-8 -*-
import controllers
import product
import account_invoice
# -*- encoding: utf-8 -*-
{
'name': 'Website Sale Digital - Sell digital products',
'version': '0.1',
'description': """
Sell digital product using attachments to virtual products
""",
'author': 'Odoo S.A.',
'depends': [
'document',
'website_sale',
],
'installable': True,
'data': [
'views/website_sale_digital.xml',
'views/website_sale_digital_view.xml',
],
'demo': [
'demo.xml',
],
}
# -*- encoding: utf-8 -*-
from openerp import models, fields, api, _
class account_invoice_line(models.Model):
_inherit = ['account.invoice.line']
def get_digital_purchases(self,uid):
user = self.env['res.users'].browse(uid)
partner = user.partner_id
# Get paid invoices
purchases = self.sudo().search_read(
domain=[('invoice_id.state', '=', 'paid'), ('invoice_id.partner_id', '=', partner.id), ('product_id.product_tmpl_id.digital_content', '=', True)],
fields=['product_id'],
)
# I only want product_ids, but search_read insists in giving me a list of
# (product_id: <id>, name: <product code> <template_name> <attributes>)
return map(lambda x: x['product_id'][0], purchases)
\ No newline at end of file
# -*- encoding: utf-8 -*-
import main
# -*- coding: utf-8 -*-
import base64
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.addons.website.controllers.main import Website
from openerp.addons.website_sale.controllers.main import website_sale
from cStringIO import StringIO
from werkzeug.utils import redirect
class website_sale_digital(website_sale):
orders_page = '/shop/orders'
@http.route([
'/shop/confirmation',
], type='http', auth="public", website=True)
def payment_confirmation(self, **post):
response = super(website_sale_digital, self).payment_confirmation(**post)
order_lines = response.qcontext['order'].order_line
digital_content = map(lambda x: x.product_id.digital_content, order_lines)
response.qcontext.update(digital=any(digital_content))
return response
@http.route([
'/shop/orders',
'/shop/orders/page/<int:page>',
], type='http', auth='user', website=True)
def orders_followup(self, page=1, **post):
response = super(website_sale_digital, self).orders_followup(**post)
order_products_attachments = {}
for o in response.qcontext['orders']:
invoiced_lines = request.env['account.invoice.line'].sudo().search([('invoice_id', 'in', o.invoice_ids.ids), ('invoice_id.state', '=', 'paid')])
purchased_products_attachments = {}
for il in invoiced_lines:
p_obj = il.product_id
# Ignore products that do not have digital content
if not p_obj.product_tmpl_id.digital_content:
continue
# Search for product attachments
A = request.env['ir.attachment']
p_id = p_obj.id
template = p_obj.product_tmpl_id
att = A.search_read(
domain=['|', '&', ('res_model', '=', p_obj._name), ('res_id', '=', p_id), '&', ('res_model', '=', template._name), ('res_id', '=', template.id)],
fields=['name', 'write_date'],
order='write_date desc',
)
# Ignore products with no attachments
if not att:
continue
purchased_products_attachments[p_id] = att
order_products_attachments[o.id] = purchased_products_attachments
response.qcontext.update({
'digital_attachments': order_products_attachments,
})
return response
@http.route([
'/shop/download',
], type='http', auth='public')
def download_attachment(self, attachment_id):
# Check if this is a valid attachment id
attachment = request.env['ir.attachment'].sudo().search_read(
[('id', '=', int(attachment_id))],
["name", "datas", "file_type", "res_model", "res_id", "type", "url"]
)
if attachment:
attachment = attachment[0]
else:
return redirect(self.orders_page)
# Check if the user has bought the associated product
res_model = attachment['res_model']
res_id = attachment['res_id']
purchased_products = request.env['account.invoice.line'].get_digital_purchases(request.uid)
if res_model == 'product.product':
if res_id not in purchased_products:
return redirect(self.orders_page)
# Also check for attachments in the product templates
elif res_model == 'product.template':
P = request.env['product.product']
template_ids = map(lambda x: P.browse(x).product_tmpl_id.id, purchased_products)
if res_id not in template_ids:
return redirect(self.orders_page)
else:
return redirect(self.orders_page)
# The client has bought the product, otherwise it would have been blocked by now
if attachment["type"] == "url":
if attachment["url"]:
return redirect(attachment["url"])
else:
return request.not_found()
elif attachment["datas"]:
data = StringIO(base64.standard_b64decode(attachment["datas"]))
return http.send_file(data, filename=attachment['name'], as_attachment=True)
else:
return request.not_found()
<odoo>
<record id="website_sale_digital.product_1" model="product.template">
<field name="name">Alice's Adventures in Wonderland - Lewis Caroll</field>
<field name="standard_price">2</field>
<field name="list_price">4.50</field>
<field name="type">service</field>
<field name="digital_content" eval="True"/>
<field name="website_published" eval="True"/>
<field name="image" type="base64" file="website_sale_digital/static/digital_product_1.jpg"/>
<field name="description">Novel by Lewis Caroll.</field>
<field name="description_sale">Alice's Adventures in Wonderland (commonly shortened to Alice in Wonderland) is an 1865 novel written by English author Charles Lutwidge Dodgson under the pseudonym Lewis Carroll. It tells of a girl named Alice falling through a rabbit hole into a fantasy world populated by peculiar, anthropomorphic creatures. The tale plays with logic, giving the story lasting popularity with adults as well as with children. It is considered to be one of the best examples of the literary nonsense genre. Its narrative course and structure, characters and imagery have been enormously influential in both popular culture and literature, especially in the fantasy genre.</field>
</record>
<record id="website_sale_digital.attach1" model="ir.attachment">
<field name="name">alice.pdf</field>
<field name="datas_fname">alice.pdf</field>
<field name="type">binary</field>
<field name="datas" type="base64" file="website_sale_digital/static/alice.pdf"/>
<field name="res_id" ref="website_sale_digital.product_1"/>
<field name="res_model">product.template</field>
</record>
</odoo>
# -*- encoding: utf-8 -*-
from openerp import models, fields, api, _
class product_template(models.Model):
_inherit = ['product.template']
digital_content = fields.Boolean('Digital Content', help="If checked, it will allow clients to download the product attachments when they have bought it.")
File added
addons/website_sale_digital/static/digital_product_1.jpg

185 KiB

<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="orders_followup_downloads" name="Orders Downloads Followup" inherit_id="website_sale.order_invoice">
<xpath expr="//div[@id='product_name']" position="inside">
<t t-set="attachments" t-value="digital_attachments[o.id].get(ol.product_id.id)"/>
<t t-if="attachments">
<span class="dropdown">
<button class="btn btn-xs btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown">
Downloads <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
<t t-foreach="attachments" t-as="a">
<li role="presentation"><a role="menuitem" tabindex="-1" t-att-href="'/shop/download?attachment_id=%i' % a['id']"><t t-esc="a['name']"/></a></li>
</t>
</ul>
</span>
</t>
</xpath>
</template>
</odoo>
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment