openerp-community-reviewer team mailing list archive
-
openerp-community-reviewer team
-
Mailing list archive
-
Message #01124
lp:~camptocamp/sale-wkfl/add-sale_validity-sale-stock-prebook-jge into lp:sale-wkfl
Joël Grand-Guillaume @ camptocamp has proposed merging lp:~camptocamp/sale-wkfl/add-sale_validity-sale-stock-prebook-jge into lp:sale-wkfl.
Commit message:
[ADD] sale_validity : Adds a validity date on the sales quotation defining until when the quotation is valid
[ADD] sale_stock_prebook : Allow to reserve stock (virtual quantity) during quotation
Requested reviews:
Sale Core Editors (sale-core-editors)
For more details, see:
https://code.launchpad.net/~camptocamp/sale-wkfl/add-sale_validity-sale-stock-prebook-jge/+merge/193602
Hi,
This proposal add 2 modules :
- sale_validity : Add a validity date on the sales quotation defining until when the quotation is valid
- sale_stock_prebook : Allow to reserve stock (virtual quantity) during quotation
I made one for both as they make sense together and sale_validity is a very small one. They are useful when people make to ensure a given stock will not be used while sending a quote to an important customer.
Regards,
--
https://code.launchpad.net/~camptocamp/sale-wkfl/add-sale_validity-sale-stock-prebook-jge/+merge/193602
Your team Sale Core Editors is requested to review the proposed merge of lp:~camptocamp/sale-wkfl/add-sale_validity-sale-stock-prebook-jge into lp:sale-wkfl.
=== added directory 'sale_stock_prebook'
=== added file 'sale_stock_prebook/__init__.py'
--- sale_stock_prebook/__init__.py 1970-01-01 00:00:00 +0000
+++ sale_stock_prebook/__init__.py 2013-11-08 10:05:23 +0000
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright 2013 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from . import model
+from . import wizard
=== added file 'sale_stock_prebook/__openerp__.py'
--- sale_stock_prebook/__openerp__.py 1970-01-01 00:00:00 +0000
+++ sale_stock_prebook/__openerp__.py 2013-11-08 10:05:23 +0000
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Jacques-Etienne Baudoux
+# Copyright 2013 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+{"name": "Sales Quotation Pre-booking of stock",
+ "version": "7.0.0",
+ "depends": ["sale_validity", "sale_stock"],
+ "author": "Camptocamp",
+ "website": "http://www.camptocamp.com",
+ "category": "Sales",
+ "description": "Allow to reserve stock (virtual quantity) during quotation",
+ 'data': ["wizard/sale_stock_prebook.xml",
+ "view/sale_order.xml"],
+ 'installable': True,
+ 'active': False,
+ }
=== added directory 'sale_stock_prebook/model'
=== added file 'sale_stock_prebook/model/__init__.py'
--- sale_stock_prebook/model/__init__.py 1970-01-01 00:00:00 +0000
+++ sale_stock_prebook/model/__init__.py 2013-11-08 10:05:23 +0000
@@ -0,0 +1,2 @@
+import sale_order
+import stock_move
=== added file 'sale_stock_prebook/model/sale_order.py'
--- sale_stock_prebook/model/sale_order.py 1970-01-01 00:00:00 +0000
+++ sale_stock_prebook/model/sale_order.py 2013-11-08 10:05:23 +0000
@@ -0,0 +1,214 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright 2013 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+"""
+Pre-book stock while sale order is not yet confirmed.
+
+Create a stock move (without picking and procurement) to decrease virtual stock.
+That reservation gets updated with the sale order line.
+If a reservation is existing at order confirmation, use it in the generated picking.
+
+"""
+import itertools
+from osv import fields, osv
+from tools.translate import _
+from openerp import SUPERUSER_ID
+
+WATCHED_KEYS = ['product_id', 'product_qty', 'product_uom', 'product_uos_qty',
+ 'product_uos', 'product_packaging', 'partner_id', 'price_unit']
+
+
+class sale_order(osv.osv):
+ _inherit = "sale.order"
+
+ _columns = {
+ 'is_prebookable': fields.function(lambda self, *args, **kwargs: self._get_is_prebooked(*args, **kwargs),
+ multi='prebooked',
+ type='boolean',
+ readonly=True,
+ string='Stock Pre-bookable',
+ help="Are there products pre-bookable?"),
+
+ 'is_prebooked': fields.function(lambda self, *args, **kwargs: self._get_is_prebooked(*args, **kwargs),
+ multi='prebooked',
+ type='boolean', readonly=True,
+ string='Stock Pre-booked', help="Are all products pre-booked in stock?",
+ invisible=True,
+ states={
+ 'draft': [('invisible', False)],
+ 'sent': [('invisible', False)],
+ })
+ }
+
+ def _get_is_prebooked(self, cr, uid, ids, fields, args, context=None):
+ """ Is pre-booked tells if order lines are mto or if there is a stock move"""
+ res = {}
+ for order_id in ids:
+ res[order_id] = {'is_prebookable': False, 'is_prebooked': False}
+ line_ids = [x['order_line'] for x in
+ self.read(cr, uid, ids, ['order_line'], context=context)]
+ line_ids = list(itertools.chain.from_iterable(line_ids))
+
+ for line in self.pool.get('sale.order.line').read(cr, uid, line_ids,
+ ['type', 'move_ids', 'order_id'],
+ context=context,
+ load='_classic_write'):
+ if line['type'] != 'make_to_order':
+ if line['move_ids']:
+ res[line['order_id']]['is_prebooked'] = True
+ else:
+ res[line['order_id']]['is_prebookable'] = True
+ return res
+
+ def _create_pickings_and_procurements(self, cr, uid, order, order_lines,
+ picking_id=False, context=None):
+ """ Delete prebookings """
+ unlink_ids = []
+ for line in order_lines:
+ for move in line.move_ids:
+ #we don't expect this method to be called outside quotation confirmation
+ assert move._model._is_prebooked(move), _("Internal Error")
+ unlink_ids.append(move.id)
+ unlink_ids and self.pool.get('sale.order.line')._prebook_cancel(cr, uid, unlink_ids, context)
+ return super(sale_order, self)._create_pickings_and_procurements(cr, uid, order,
+ order_lines,
+ picking_id=picking_id,
+ context=context)
+
+ def button_prebook(self, cr, uid, ids, context):
+ orders = self.read(cr, uid, ids, ['is_prebookable'], context=context)
+ if not any((x['is_prebookable'] for x in orders)):
+ raise osv.except_osv(_('Warning!'),
+ _('All products are already pre-booked in stock.'))
+ return {
+ 'name': _('Pre-book products from stock'),
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'sale.stock.prebook',
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'target': 'new',
+ }
+
+ def button_prebook_cancel(self, cr, uid, ids, context):
+ line_ids = [x['order_line'] for x in
+ self.read(cr, SUPERUSER_ID, ids, ['order_line'], context=context)]
+ line_ids = list(itertools.chain.from_iterable(line_ids))
+ line_ids and self.pool.get('sale.order.line')._prebook_cancel(cr, uid,
+ line_ids,
+ context=context)
+
+
+class sale_order_line(osv.osv):
+ _inherit = "sale.order.line"
+ _columns = {'date_prebooked': fields.function(lambda self, *args, **kwargs: self._get_date_prebooked(*args, **kwargs),
+ multi='prebooked',
+ type='date', readonly=True,
+ string='Pre-booked Stock Until',
+ invisible=True,
+ states={
+ 'draft': [('invisible', False)],
+ 'sent': [('invisible', False)],
+ })}
+
+ def _get_date_prebooked(self, cr, uid, ids, fields, args, context=None):
+ res = {}
+ for line in self.browse(cr, uid, ids, context=context):
+ res[line.id] = {'date_prebooked': False}
+ if line.move_ids:
+ for move in line.move_ids:
+ if move._model._is_prebooked(move):
+ res[line.id]['date_prebooked'] = move.date_validity
+ break
+ return res
+
+ def _prebook(self, cr, uid, ids, date_prebook, context=None):
+ sale_obj = self.pool.get('sale.order')
+ move_obj = self.pool.get('stock.move')
+ for line in self.browse(cr, SUPERUSER_ID, ids, context=context):
+ order = line.order_id
+ picking_id = False
+ date_planned = sale_obj._get_date_planned(cr, uid, order, line,
+ order.date_order,
+ context=context)
+ d_move = sale_obj._prepare_order_line_move(cr, uid, order, line,
+ picking_id, date_planned,
+ context=None)
+ d_move['state'] = 'waiting' # Ensure move don't get processed
+ d_move['date_validity'] = date_prebook
+ if line.move_ids: # There are already moves linked to the line, don't create new one
+ for move in line.move_ids:
+ assert move._model._is_prebooked(move), _("Internal Error")
+ move_ids = [x['id'] for x in line.move_ids]
+ move_obj.write(cr, uid, move_ids, d_move, context=context)
+ subject = "Updated pre-booking for %s" % line.name
+ else:
+ move_obj.create(cr, uid, d_move, context=context)
+ subject = "Pre-booking for %s" % line.name
+ message = "Stock has been pre-booked until %s" % date_prebook
+ sale_obj.message_post(cr, uid, [order.id], subject=subject,
+ body=message, context=context)
+
+ def _prebook_cancel(self, cr, uid, ids, context=None):
+ if context is None:
+ context = {}
+ move_ids = []
+ for line in self.browse(cr, SUPERUSER_ID, ids, context=context):
+ move_ids += [x['id'] for x in line.move_ids if x._model._is_prebooked(x)]
+ if move_ids:
+ ctx = context.copy()
+ ctx['call_unlink'] = True # Allows to delete non draft moves
+ self.pool.get('stock.move').unlink(cr, uid, move_ids, context=ctx)
+
+ def write(self, cr, uid, ids, vals, context=None):
+ """Update pre-booking when line is changed"""
+ res = super(sale_order_line, self).write(cr, uid, ids, vals, context=context)
+ # Remove pre-booking if procurement method changed to mto
+ if vals.get('type', '') == 'make_to_order':
+ self._prebook_cancel(cr, uid, ids, context=context)
+ # Update pre-booking if fields changed
+ else:
+ fields = WATCHED_KEYS
+ if not set(vals.keys()).intersection(set(fields)):
+ return res
+ for line in self.read(cr, uid, ids, ['date_prebooked'], context=context):
+ if line['date_prebooked']:
+ self._prebook(cr, uid, [line['id']], line['date_prebooked'], context=context)
+ return res
+
+ #TODO: remove pre-booking if cancelled
+ #def ...
+
+ def button_prebook_update(self, cr, uid, ids, context):
+ if context is None:
+ context = {}
+ line = self.read(cr, uid, ids[0], ['date_prebooked'], context=context)
+ ctx = context.copy()
+ ctx['default_date'] = line['date_prebooked']
+ return {
+ 'name': _('Pre-book products from stock'),
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'sale.stock.prebook',
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'target': 'new',
+ 'context': ctx,
+ }
+
+ def button_prebook_cancel(self, cr, uid, ids, context):
+ self._prebook_cancel(cr, uid, ids, context=context)
=== added file 'sale_stock_prebook/model/stock_move.py'
--- sale_stock_prebook/model/stock_move.py 1970-01-01 00:00:00 +0000
+++ sale_stock_prebook/model/stock_move.py 2013-11-08 10:05:23 +0000
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Nicolas Bessi
+# Copyright 2013 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+from osv import fields, osv
+"""Add expiry date on pre-booked stock move"""
+
+
+class stock_move(osv.osv):
+ _inherit = "stock.move"
+
+ _columns = {
+ 'date_validity': fields.date('Validity Date'),
+ }
+
+ def init(self, cr):
+ """ Index date_validity if filled """
+ indexname = 'stock_move_date_validity_index'
+ cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', (indexname,))
+ if not cr.fetchone():
+ cr.execute("""CREATE INDEX %s ON %s (date_validity)
+ WHERE date_validity IS NOT NULL""" % (indexname, self._table))
+
+ def _is_prebooked(self, move):
+ return move.date_validity and move.state == 'waiting' and move.picking_id.id is False
=== added directory 'sale_stock_prebook/view'
=== added file 'sale_stock_prebook/view/sale_order.xml'
--- sale_stock_prebook/view/sale_order.xml 1970-01-01 00:00:00 +0000
+++ sale_stock_prebook/view/sale_order.xml 2013-11-08 10:05:23 +0000
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<openerp>
+<data>
+
+ <record id="view_order_form_prebook" model="ir.ui.view">
+ <field name="name">sale.order.form.prebook</field>
+ <field name="model">sale.order</field>
+ <field name="type">form</field>
+ <field name="inherit_id" ref="sale_validity.view_order_form_validity"/>
+ <field name="arch" type="xml">
+ <button name="action_quotation_send" position="before">
+ <button name="button_prebook" type="object"
+ states="draft,sent"
+ string="Pre-book stock" help="Pre-book products from stock"
+ context="{'default_date':date_validity}"
+ />
+ </button>
+
+ <xpath expr="//field[@name='order_line']//field[@name='state']" position="before">
+ <button name="%(action_sale_stock_prebook)d" type="action" states="draft" string="Pre-book stock" context="{'default_date':parent.date_validity}" attrs="{'invisible':[('date_prebooked','!=',False)]}" />
+ </xpath>
+ <xpath expr="//field[@name='order_line']//field[@name='type']" position="after">
+ <label for="date_prebooked" attrs="{'invisible':[('type','=','make_to_order')]}"/>
+ <div attrs="{'invisible':[('type','=','make_to_order')]}">
+ <field name="date_prebooked" class="oe_inline"/>
+ <button name="button_prebook_update" string="update" type="object" class="oe_link" attrs="{'invisible':[('date_prebooked','=',False)]}"/>
+ -
+ <button name="button_prebook_cancel" string="cancel" type="object" class="oe_link" attrs="{'invisible':[('date_prebooked','=',False)]}"/>
+ </div>
+ </xpath>
+
+ <field name="invoiced" position="before">
+ <label for="is_prebooked"/><div>
+ <field name="is_prebooked"/>
+ <button name="button_prebook_cancel" string="cancel all" type="object" class="oe_link" attrs="{'invisible':[('is_prebooked','=',False)]}"/>
+ </div>
+ </field>
+ </field>
+ </record>
+
+</data>
+</openerp>
=== added directory 'sale_stock_prebook/wizard'
=== added file 'sale_stock_prebook/wizard/__init__.py'
--- sale_stock_prebook/wizard/__init__.py 1970-01-01 00:00:00 +0000
+++ sale_stock_prebook/wizard/__init__.py 2013-11-08 10:05:23 +0000
@@ -0,0 +1,1 @@
+import sale_stock_prebook
=== added file 'sale_stock_prebook/wizard/sale_stock_prebook.py'
--- sale_stock_prebook/wizard/sale_stock_prebook.py 1970-01-01 00:00:00 +0000
+++ sale_stock_prebook/wizard/sale_stock_prebook.py 2013-11-08 10:05:23 +0000
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+from osv import osv, fields
+from tools.translate import _
+from openerp import SUPERUSER_ID
+
+class sale_stock_prebook(osv.osv_memory):
+ _name = 'sale.stock.prebook'
+ _columns = {
+ 'date': fields.date("Pre-book stock until", help="Define date until when stock will be pre-booked", required=True),
+ }
+ def button_confirm(self, cr, uid, ids, context):
+ wiz=self.browse(cr,uid,ids[0],context=context)
+ if context['active_model']=='sale.order':
+ order_line_ids=map(sum,[x['order_line'] for x in self.pool.get('sale.order').read(cr,uid,context['active_ids'],['order_line'],context=context,load='_classic_write')])
+ self.pool.get('sale.order.line')._prebook(cr,uid,order_line_ids,wiz.date,context=context)
+ elif context['active_model']=='sale.order.line':
+ self.pool.get('sale.order.line')._prebook(cr,uid,context['active_ids'],wiz.date,context=context)
+ return {'type': 'ir.actions.act_window_close'}
=== added file 'sale_stock_prebook/wizard/sale_stock_prebook.xml'
--- sale_stock_prebook/wizard/sale_stock_prebook.xml 1970-01-01 00:00:00 +0000
+++ sale_stock_prebook/wizard/sale_stock_prebook.xml 2013-11-08 10:05:23 +0000
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+<data>
+ <record id="view_sale_stock_prebook_form" model="ir.ui.view">
+ <field name="name">sale.stock.prebook.form</field>
+ <field name="model">sale.stock.prebook</field>
+ <field name="arch" type="xml">
+ <form string="Pre-book stock" version="7.0">
+ <group colspan="4">
+ <field name="date"/>
+ </group>
+ <footer>
+ <button icon="gtk-execute" string="Confirm" name="button_confirm" type="object" class="oe_highlight" />
+ or
+ <button icon="gtk-cancel" special="cancel" string="Cancel"/>
+ </footer>
+ </form>
+ </field>
+ </record>
+ <record id="action_sale_stock_prebook" model="ir.actions.act_window">
+ <field name="name">Pre-book products from stock</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">sale.stock.prebook</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">form</field>
+ <field name="target">new</field>
+ </record>
+
+</data>
+</openerp>
=== added directory 'sale_validity'
=== added file 'sale_validity/__init__.py'
--- sale_validity/__init__.py 1970-01-01 00:00:00 +0000
+++ sale_validity/__init__.py 2013-11-08 10:05:23 +0000
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright 2013 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from . import model
=== added file 'sale_validity/__openerp__.py'
--- sale_validity/__openerp__.py 1970-01-01 00:00:00 +0000
+++ sale_validity/__openerp__.py 2013-11-08 10:05:23 +0000
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Author: Jacques-Etienne Baudoux
+# Copyright 2013 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+{"name": "Sales Quotation Validity Date",
+ "version": "7.0.0",
+ "depends": ["sale"],
+ "author": "Camptocamp",
+ "category": "Sales",
+ "website": "http://www.camptocamp.com",
+ "description": """
+Sale order validity date
+========================
+
+Add a validity date on the sales quotation defining
+until when the quotation is valid
+
+""",
+ 'data': ["view/sale_order.xml"],
+ 'installable': True,
+ 'active': False,
+ }
=== added directory 'sale_validity/model'
=== added file 'sale_validity/model/__init__.py'
--- sale_validity/model/__init__.py 1970-01-01 00:00:00 +0000
+++ sale_validity/model/__init__.py 2013-11-08 10:05:23 +0000
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright 2013 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from . import sale_order
=== added file 'sale_validity/model/sale_order.py'
--- sale_validity/model/sale_order.py 1970-01-01 00:00:00 +0000
+++ sale_validity/model/sale_order.py 2013-11-08 10:05:23 +0000
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright 2013 Camptocamp SA
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# 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 Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+from osv import fields, osv
+
+
+class sale_order(osv.osv):
+ _inherit = "sale.order"
+
+ _columns = {'date_validity': fields.date("Valid Until",
+ help="Define date until when quotation is valid",
+ readonly=True,
+ states={
+ 'draft': [('readonly', False)],
+ 'sent': [('readonly', True)],
+ },
+ track_visibility='onchange')}
=== added directory 'sale_validity/view'
=== added file 'sale_validity/view/sale_order.xml'
--- sale_validity/view/sale_order.xml 1970-01-01 00:00:00 +0000
+++ sale_validity/view/sale_order.xml 2013-11-08 10:05:23 +0000
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<openerp>
+<data>
+
+ <record id="view_order_form_validity" model="ir.ui.view">
+ <field name="name">sale.order.form.validity</field>
+ <field name="model">sale.order</field>
+ <field name="type">form</field>
+ <field name="inherit_id" ref="sale.view_order_form"/>
+ <field name="arch" type="xml">
+ <field name="date_order" position="after">
+ <field name="date_validity"/>
+ </field>
+ </field>
+ </record>
+
+</data>
+</openerp>
Follow ups