[IMP] website sales salesteam: refactored implementation

- now having a website sales sales team in website_sale;
- temporarily crm accepted as a dependency of website_sale (will be improved when sales team are
taken out of crm);
- removed website_sale_crm unnecessary module + dead code
- removed shopping cart state + its management
- added acquirer and transaction fields on sale order
- azdded use_opportunities and use_quotations fields on sales team like use_leads
This commit is contained in:
Thibault Delavallée 2014-05-12 17:52:10 +02:00
parent 46235aa78c
commit 2b8739cf8b
18 changed files with 56 additions and 177 deletions

View File

@ -176,7 +176,7 @@ class crm_case_section(osv.osv):
'color': fields.integer('Color Index'),
'use_leads': fields.boolean('Leads',
help="The first contact you get with a potential customer is a lead you qualify before converting it into a real business opportunity. Check this box to manage leads in this sales team."),
'use_opportunities': fields.boolean('Opportunities', help="Check this box to manage opportunities in this sales team."),
'monthly_open_leads': fields.function(_get_opportunities_data,
type="string", readonly=True, multi='_get_opportunities_data',
string='Open Leads per Month'),
@ -193,6 +193,7 @@ class crm_case_section(osv.osv):
'active': 1,
'stage_ids': _get_stage_common,
'use_leads': True,
'use_opportunities': True,
}
_sql_constraints = [

View File

@ -87,6 +87,7 @@
<field name="arch" type="xml">
<kanban version="7.0" class="oe_background_grey">
<field name="use_leads"/>
<field name="use_opportunities"/>
<field name="name"/>
<field name="user_id"/>
<field name="member_ids"/>
@ -119,7 +120,7 @@
options="{'height': '20px', 'barWidth': 4, 'barSpacing': 1, 'delayIn': '3000', 'tooltip_suffix': ' Leads'}">Open Leads per Month<br/>Click to see a detailed analysis of leads.</field>
</a>
</div>
<div class="oe_salesteams_opportunities">
<div class="oe_salesteams_opportunities" t-if="record.use_opportunities.raw_value">
<a name="%(crm_case_form_view_salesteams_opportunity)d" type="action">Opportunities</a>
<a name="%(action_report_crm_opportunity_salesteam)d" type="action">
<field name="monthly_planned_revenue" widget="sparkline_bar"
@ -197,6 +198,7 @@
</h1>
<div name="options_active">
<field name="use_leads" class="oe_inline"/><label for="use_leads"/>
<field name="use_opportunities" class="oe_inline"/><label for="use_opportunities"/>
</div>
</div>
<group>

View File

@ -74,7 +74,7 @@ class sale_order(osv.Model):
if not grid_id:
raise osv.except_osv(_('No Grid Available!'), _('No grid matching for this carrier!'))
if order.state in ['sent', 'cancel', 'progress', 'manual', 'done']:
if order.state != 'draft':
raise osv.except_osv(_('Order not in Draft State!'), _('The order state have to be draft to add delivery lines.'))
grid = grid_obj.browse(cr, uid, grid_id, context=context)

View File

@ -27,18 +27,6 @@ from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FO
import openerp.addons.decimal_precision as dp
from openerp import workflow
AVAILABLE_STATES = [
('draft', 'Draft Quotation'),
('sent', 'Quotation Sent'),
('cancel', 'Cancelled'),
('waiting_date', 'Waiting Schedule'),
('progress', 'Sales Order'),
('manual', 'Sale to Invoice'),
('shipping_except', 'Shipping Exception'),
('invoice_except', 'Invoice Exception'),
('done', 'Done'),
]
class sale_order(osv.osv):
_name = "sale.order"
_inherit = ['mail.thread', 'ir.needaction_mixin']
@ -180,7 +168,17 @@ class sale_order(osv.osv):
readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, select=True),
'origin': fields.char('Source Document', size=64, help="Reference of the document that generated this sales order request."),
'client_order_ref': fields.char('Reference/Description', size=64),
'state': fields.selection(AVAILABLE_STATES , 'Status', readonly=True, help="Gives the status of the quotation or sales order.\
'state': fields.selection([
('draft', 'Draft Quotation'),
('sent', 'Quotation Sent'),
('cancel', 'Cancelled'),
('waiting_date', 'Waiting Schedule'),
('progress', 'Sales Order'),
('manual', 'Sale to Invoice'),
('shipping_except', 'Shipping Exception'),
('invoice_except', 'Invoice Exception'),
('done', 'Done'),
], 'Status', readonly=True, help="Gives the status of the quotation or sales order.\
\nThe exception status is automatically set when a cancel operation occurs \
in the invoice validation (Invoice Exception) or in the picking list process (Shipping Exception).\nThe 'Waiting Schedule' status is set when the invoice is confirmed\
but waiting for the scheduler to run on the order date.", select=True),

View File

@ -76,7 +76,7 @@ class crm_case_section(osv.osv):
date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1]).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
for id in ids:
res[id] = dict()
created_domain = [('section_id', '=', id), ('state', '=', ['draft']), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)]
created_domain = [('section_id', '=', id), ('state', '=', 'draft'), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)]
res[id]['monthly_quoted'] = self.__get_bar_values(cr, uid, obj, created_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context)
validated_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'sent', 'cancel']), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)]
res[id]['monthly_confirmed'] = self.__get_bar_values(cr, uid, obj, validated_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context)
@ -94,6 +94,7 @@ class crm_case_section(osv.osv):
return res
_columns = {
'use_quotations': fields.boolean('Opportunities', help="Check this box to manage quotations in this sales team."),
'invoiced_forecast': fields.integer(string='Invoice Forecast',
help="Forecast of the invoice revenue for the current month. This is the amount the sales \n"
"team should invoice this month. It is used to compute the progression ratio \n"
@ -112,6 +113,10 @@ class crm_case_section(osv.osv):
string='Rate of sent invoices per duration'),
}
_defaults = {
'use_quotations': True,
}
def action_forecast(self, cr, uid, id, value, context=None):
return self.write(cr, uid, [id], {'invoiced_forecast': round(float(value))}, context=context)

View File

@ -244,6 +244,9 @@
<field name="inherit_id" ref="crm.crm_case_section_view_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//div[@name='options_active']" position="inside">
<field name="use_quotations" class="oe_inline"/><label for="use_quotations"/>
</xpath>
<xpath expr="//field[@name='code']" position="after">
<field name="invoiced_target"/>
<field name="invoiced_forecast"/>
@ -259,6 +262,7 @@
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='name']" position="after">
<field name="use_quotations"/>
<field name="monthly_quoted"/>
<field name="monthly_confirmed"/>
<field name="monthly_invoiced"/>
@ -284,7 +288,7 @@
</field>
</a>
</div>
<div class="oe_salesteams_quotations">
<div class="oe_salesteams_quotations" t-if="record.use_quotations.raw_value">
<a name="%(action_quotations_salesteams)d" type="action" class="oe_sparkline_bar_link">Quotations</a>
<a name="%(action_order_report_quotation_salesteam)d" type="action" class="oe_sparkline_bar_link">
<field name="monthly_quoted" widget="sparkline_bar" options="{'delayIn': '3000'}">

View File

@ -253,14 +253,14 @@ class website_sale(http.Controller):
def checkout_redirection(self, order):
cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
# must have a shopping_cart state of sale order with lines at this point, otherwise reset
if not order or order.state != 'shopping_cart':
# must have a draft state of sale order with lines at this point, otherwise reset
if not order or order.state != 'draft':
request.website_sale_reset(cr, uid, context=context)
return request.redirect('/shop')
# if transaction pending / done: redirect to confirmation
tx = context.get('website_sale_transaction')
if tx and tx.state != 'shopping_cart':
if tx and tx.state != 'draft':
return request.redirect('/shop/payment/confirmation/%s' % order.id)
def checkout_values(self, data=None):
@ -454,9 +454,9 @@ class website_sale(http.Controller):
""" Payment step. This page proposes several payment means based on available
payment.acquirer. State at this point :
- a shopping_cart state sale order with lines; otherwise, clean context / session and
- a draft state sale order with lines; otherwise, clean context / session and
back to the shop
- no transaction in context / session, or only a shopping_cart one, if the customer
- no transaction in context / session, or only a draft one, if the customer
did go to a payment.acquirer website but closed the tab without
paying / canceling
"""
@ -518,6 +518,7 @@ class website_sale(http.Controller):
cr, uid, context = request.cr, request.uid, request.context
payment_obj = request.registry.get('payment.acquirer')
transaction_obj = request.registry.get('payment.transaction')
sale_order_obj = request.registry['sale.order']
order = request.website.sale_get_order(context=context)
if not order or not order.order_line or acquirer_id is None:
@ -539,11 +540,20 @@ class website_sale(http.Controller):
'sale_order_id': order.id,
}, context=context)
request.session['sale_transaction_id'] = tx_id
elif tx and tx.state == 'shopping_cart': # button cliked but no more info -> rewrite on tx or create a new one ?
elif tx and tx.state == 'draft': # button cliked but no more info -> rewrite on tx or create a new one ?
tx.write({
'acquirer_id': acquirer_id,
})
# update quotation
sale_order_obj.write(
cr, SUPERUSER_ID, [order.id], {
'payment_acquirer_id': acquirer_id,
'payment_tx_id': request.session['sale_transaction_id']
}, context=context)
# confirm the quotation
sale_order_obj.action_button_confirm(cr, SUPERUSER_ID, [order.id], context=request.context)
acquirer_form_post_url = payment_obj.get_form_action_url(cr, uid, acquirer_id, context=context)
acquirer_total_url = '%s?%s' % (acquirer_form_post_url, werkzeug.url_encode(post))
return request.redirect(acquirer_total_url)
@ -622,9 +632,7 @@ class website_sale(http.Controller):
if not tx or not order:
return request.redirect('/shop')
if not order.amount_total or tx.state == 'done':
# confirm the quotation
sale_order_obj.action_button_confirm(cr, SUPERUSER_ID, [order.id], context=request.context)
if not order.amount_total or tx.state in ['pending', 'done']:
# send by email
email_act = sale_order_obj.action_quotation_send(cr, SUPERUSER_ID, [order.id], context=request.context)
elif tx.state == 'cancel':

View File

@ -32,10 +32,12 @@
<field name="html_class">oe_image_full</field>
</record>
<record model="crm.case.section" id="crm_case_section_shopping_cart">
<field name="name">Shopping Cart</field>
<field name="code">SPC</field>
<record model="crm.case.section" id="salesteam_website_sales">
<field name="name">Website Sales</field>
<field name="code">WS</field>
<field name="member_ids" eval="[(4, ref('base.user_root'))]"/>
<field name="use_opportunities" eval="False"/>
<field name="use_quotations" eval="False"/>
</record>
</data>

View File

@ -548,7 +548,7 @@ Weight: 1.1 ounces</field>
<field name="sequence">1</field>
</record>
<record id="crm_case_section_shopping_cart" model="crm.case.section">
<record id="salesteam_website_sales" model="crm.case.section">
<field name="member_ids" eval="[(4, ref('base.user_root'), ref('base.user_demo'))]"/>
</record>

View File

@ -4,9 +4,7 @@ import random
from openerp import SUPERUSER_ID
from openerp.osv import osv, orm, fields
from openerp.addons.web.http import request
from openerp.addons.sale import sale
sale.AVAILABLE_STATES.insert(1,('shopping_cart', 'Shopping Cart'))
class payment_transaction(orm.Model):
_inherit = 'payment.transaction'
@ -20,7 +18,7 @@ class sale_order(osv.Model):
_inherit = "sale.order"
def _cart_qty(self, cr, uid, ids, field_name, arg, context=None):
res = dict();
res = dict()
for order in self.browse(cr, uid, ids, context=context):
res[order.id] = int(sum(l.product_uom_qty for l in (order.website_order_line or [])))
return res
@ -31,8 +29,9 @@ class sale_order(osv.Model):
string='Order Lines displayed on Website', readonly=True,
help='Order Lines to be displayed on the website. They should not be used for computation purpose.',
),
'cart_quantity': fields.function(_cart_qty, type='integer', string='Main Menu'),
'state': fields.selection(sale.AVAILABLE_STATES, 'Status'),
'cart_quantity': fields.function(_cart_qty, type='integer', string='Cart Quantity'),
'payment_acquirer_id': fields.many2one('payment.acquirer', 'Payment Provider', on_delete='set null'),
'payment_tx_id': fields.many2one('payment.transaction', 'Payment Tx', on_delete='set null'),
}
def _get_errors(self, cr, uid, order, context=None):
@ -142,8 +141,7 @@ class website(orm.Model):
'user_id': w.user_id.id,
'partner_id': partner.id,
'pricelist_id': partner.property_product_pricelist.id,
'state': 'shopping_cart',
'section_id': self.pool.get('ir.model.data').get_object_reference(cr, uid, 'website_sale', 'crm_case_section_shopping_cart')[1],
'section_id': self.pool.get('ir.model.data').get_object_reference(cr, uid, 'website_sale', 'salesteam_website_sales')[1],
}
sale_order_id = sale_order_obj.create(cr, SUPERUSER_ID, values, context=context)
values = sale_order_obj.onchange_partner_id(cr, SUPERUSER_ID, [], partner.id, context=context)['value']

View File

@ -91,79 +91,5 @@
</field>
</record>
<record id="sale_order_form_state_button" model="ir.ui.view">
<field name="name">sale.order.form.state.button</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<field name='state' position="before">
<button class="oe_highlight" name="action_button_confirm" states="shopping_cart" string="Confirm Sale" type="object" groups="base.group_user"/>
<button name="cancel" states="shopping_cart" string="Cancel Quotation" groups="base.group_user"/>
</field>
</field>
</record>
<record id="sale.action_orders" model="ir.actions.act_window">
<field name="domain">[('state', 'not in', ('draft', 'shopping_cart', 'sent', 'cancel'))]</field>
</record>
<record id="view_shopping_cart_order_filter" model="ir.ui.view">
<field name="name">shopping.cart.order.filter</field>
<field name="model">sale.order</field>
<field name="arch" type="xml">
<search string="Search Sales Order">
<field name="name" string="Sales Order" filter_domain="['|',('name','ilike',self),('client_order_ref','ilike',self)]"/>
<filter icon="terp-mail-message-new" string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<filter string="My" domain="[('user_id','=',uid)]" help="My Shopping Cart Orders" icon="terp-personal" name="my_sale_orders_filter"/>
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
<field name="user_id"/>
<group expand="0" string="Group By...">
<filter string="Customer" icon="terp-personal" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Salesperson" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Order Month" icon="terp-go-month" domain="[]" context="{'group_by':'date_order'}"/>
</group>
</search>
</field>
</record>
<record id="action_shopping_cart_orders" model="ir.actions.act_window">
<field name="name">Sales Orders</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sale.order</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar,graph</field>
<field name="search_view_id" ref="view_shopping_cart_order_filter"/>
<field name="context">{
'search_default_my_sale_orders_filter': 1
}
</field>
<field name="domain">[('state', 'not in', ('draft', 'shopping_cart', 'sent', 'cancel'))]</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a quotation that can be converted into a sales
order.
</p><p>
OpenERP will help you efficiently handle the complete sales flow:
quotation, sales order, delivery, invoicing and payment.
</p>
</field>
</record>
<record id="action_shopping_cart_quotations" model="ir.actions.act_window">
<field name="name">Shopping Cart</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sale.order</field>
<field name="view_type">form</field>
<field name="view_id" ref="sale.view_quotation_tree"/>
<field name="view_mode">tree,form,calendar,graph</field>
<field name="domain">[('state','=','shopping_cart')]</field>
<field name="search_view_id" ref="view_shopping_cart_order_filter"/>
</record>
<menuitem id="menu_shopping_cart_quotations"
action="action_shopping_cart_quotations" parent="base.menu_sales"
sequence="5"/>
</data>
</openerp>

View File

@ -1,15 +0,0 @@
{
'name': 'eCommerce with CRM',
'version': '0.1',
'category': 'Hidden',
'description': """
""",
'author': 'OpenERP SA',
'depends': ['website_sale', 'sale_crm'],
'data': [
'data/website_sale_crm.xml',
],
'installable': True,
'auto_install': True
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record model="crm.case.section" id="website.section_sales_department">
<field name="name">Website</field>
<field name="code">Website</field>
<field name="alias_name">Website</field>
<field name="member_ids" eval="[(4, ref('base.user_root'))]"/>
</record>
</data>
</openerp>

View File

@ -1 +0,0 @@
import website

View File

@ -1,14 +0,0 @@
# -*- coding: utf-8 -*-
from openerp.osv import orm
from openerp import SUPERUSER_ID
class Website(orm.Model):
_inherit = 'website'
def _ecommerce_create_quotation(self, cr, uid, context=None):
order_id = super(Website, self)._ecommerce_create_quotation(cr, uid, context=context)
section_ids = self.pool["crm.case.section"].search(cr, SUPERUSER_ID, [("code", "=", "Website")], context=context)
if section_ids:
self.pool["sale.order"].write(cr, SUPERUSER_ID, {'section_id': section_ids[0]}, context=context)
return order_id

View File

@ -1,2 +1 @@
import sale_order
import website

View File

@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
from openerp.osv import orm
from openerp import SUPERUSER_ID
class Website(orm.Model):
_inherit = 'website'
def _ecommerce_create_quotation(self, cr, uid, context=None):
order_id = super(Website, self)._ecommerce_create_quotation(cr, uid, context=context)
order = self.pool['sale.order'].browse(cr, SUPERUSER_ID, order_id, context=context)
self.pool['sale.order']._check_carrier_quotation(cr, uid, order, force_carrier_id=None, context=context)
return order_id
def _ecommerce_add_product_to_cart(self, cr, uid, product_id=0, order_line_id=0, number=1, set_number=-1, context=None):
quantity = super(Website, self)._ecommerce_add_product_to_cart(cr, uid,
product_id=product_id, order_line_id=order_line_id, number=number, set_number=set_number,
context=context)
order = self.ecommerce_get_current_order(cr, uid, context=context)
self.pool['sale.order']._check_carrier_quotation(cr, uid, order, force_carrier_id=None, context=context) and quantity or None
return quantity