Merge lp:~unifield-team/unifield-addons/new-purchase-order-state into lp:unifield-addons
- new-purchase-order-state
- Merge into trunk
Proposed by
Quentin THEURET @Amaris
Status: | Merged |
---|---|
Merged at revision: | 4457 |
Proposed branch: | lp:~unifield-team/unifield-addons/new-purchase-order-state |
Merge into: | lp:unifield-addons |
Diff against target: |
1069 lines (+996/-0) 14 files modified
procurement_list/__init__.py (+25/-0) procurement_list/__openerp__.py (+53/-0) procurement_list/procurement_list.py (+217/-0) procurement_list/procurement_list_sequence.xml (+20/-0) procurement_list/procurement_list_view.xml (+111/-0) procurement_list/procurement_list_wizard.xml (+21/-0) procurement_list/security/ir.model.access.csv (+3/-0) procurement_list/test/list_import.csv (+5/-0) procurement_list/test/procurement_list.yml (+143/-0) procurement_list/wizard/__init__.py (+28/-0) procurement_list/wizard/wizard_import_list.py (+196/-0) procurement_list/wizard/wizard_import_list_view.xml (+48/-0) procurement_list/wizard/wizard_list_to_order.py (+63/-0) procurement_list/wizard/wizard_list_to_rfq.py (+63/-0) |
To merge this branch: | bzr merge lp:~unifield-team/unifield-addons/new-purchase-order-state |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
UniField Dev Team | Pending | ||
Review via email: mp+53577@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
- 4469. By Quentin THEURET @Amaris
-
UF-98: [FIX] Fixed bug on confirmation of a purchase order with a line which hasn't linked procurement list line
- 4470. By Quentin THEURET @Amaris
-
UF-98: [ADD] Added csv file for import test
- 4471. By Quentin THEURET <quentin@tempo-quentin>
-
UF-98: [IMP] Added copy method to procurement.list to increase sequence and set empty the latest field of lines
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added directory 'procurement_list' |
2 | === added file 'procurement_list/__init__.py' |
3 | --- procurement_list/__init__.py 1970-01-01 00:00:00 +0000 |
4 | +++ procurement_list/__init__.py 2011-03-16 16:41:25 +0000 |
5 | @@ -0,0 +1,25 @@ |
6 | +# -*- coding: utf-8 -*- |
7 | +############################################################################## |
8 | +# |
9 | +# OpenERP, Open Source Management Solution |
10 | +# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). |
11 | +# |
12 | +# This program is free software: you can redistribute it and/or modify |
13 | +# it under the terms of the GNU Affero General Public License as |
14 | +# published by the Free Software Foundation, either version 3 of the |
15 | +# License, or (at your option) any later version. |
16 | +# |
17 | +# This program is distributed in the hope that it will be useful, |
18 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
20 | +# GNU Affero General Public License for more details. |
21 | +# |
22 | +# You should have received a copy of the GNU Affero General Public License |
23 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
24 | +# |
25 | +############################################################################## |
26 | + |
27 | +import procurement_list |
28 | +import wizard |
29 | + |
30 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
31 | |
32 | === added file 'procurement_list/__openerp__.py' |
33 | --- procurement_list/__openerp__.py 1970-01-01 00:00:00 +0000 |
34 | +++ procurement_list/__openerp__.py 2011-03-16 16:41:25 +0000 |
35 | @@ -0,0 +1,53 @@ |
36 | +# -*- coding: utf-8 -*- |
37 | +############################################################################## |
38 | +# |
39 | +# OpenERP, Open Source Management Solution |
40 | +# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). |
41 | +# |
42 | +# This program is free software: you can redistribute it and/or modify |
43 | +# it under the terms of the GNU Affero General Public License as |
44 | +# published by the Free Software Foundation, either version 3 of the |
45 | +# License, or (at your option) any later version. |
46 | +# |
47 | +# This program is distributed in the hope that it will be useful, |
48 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
49 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
50 | +# GNU Affero General Public License for more details. |
51 | +# |
52 | +# You should have received a copy of the GNU Affero General Public License |
53 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
54 | +# |
55 | +############################################################################## |
56 | +{ |
57 | + 'name' : 'Purchase list', |
58 | + 'version' : '1.0', |
59 | + 'author' : 'TeMPO Consulting, MSF', |
60 | + 'category': 'Generic Modules/Sales & Purchases', |
61 | + 'description': ''' |
62 | + This module allows you to create a list of items to procure. You can create automatically RfQ for these lists after choosing a list \ |
63 | + of suppliers. You can also compare these RfQ, choose the best supplier for each product and create automatically the associated \ |
64 | + purchase orders. |
65 | + ''', |
66 | + 'website': 'http://unifield.msf.org', |
67 | + 'init_xml': [ |
68 | + ], |
69 | + 'depends' : [ |
70 | + 'purchase', |
71 | + ], |
72 | + 'update_xml': [ |
73 | + 'procurement_list_sequence.xml', |
74 | + 'procurement_list_view.xml', |
75 | + 'procurement_list_wizard.xml', |
76 | + 'wizard/wizard_import_list_view.xml', |
77 | + 'security/ir.model.access.csv', |
78 | + ], |
79 | + 'demo_xml': [ |
80 | + ], |
81 | + 'test': [ |
82 | + 'test/procurement_list.yml', |
83 | + 'test/procurement_copy.yml', |
84 | + ], |
85 | + 'installable': True, |
86 | + 'active': False, |
87 | +} |
88 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
89 | |
90 | === added file 'procurement_list/procurement_list.py' |
91 | --- procurement_list/procurement_list.py 1970-01-01 00:00:00 +0000 |
92 | +++ procurement_list/procurement_list.py 2011-03-16 16:41:25 +0000 |
93 | @@ -0,0 +1,217 @@ |
94 | +#!/usr/bin/env python |
95 | +# -*- encoding: utf-8 -*- |
96 | +############################################################################## |
97 | +# |
98 | +# OpenERP, Open Source Management Solution |
99 | +# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). |
100 | +# |
101 | +# This program is free software: you can redistribute it and/or modify |
102 | +# it under the terms of the GNU Affero General Public License as |
103 | +# published by the Free Software Foundation, either version 3 of the |
104 | +# License, or (at your option) any later version. |
105 | +# |
106 | +# This program is distributed in the hope that it will be useful, |
107 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
108 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
109 | +# GNU Affero General Public License for more details. |
110 | +# |
111 | +# You should have received a copy of the GNU Affero General Public License |
112 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
113 | +# |
114 | +############################################################################## |
115 | + |
116 | +import time |
117 | +import netsvc |
118 | + |
119 | +from osv import osv |
120 | +from osv import fields |
121 | +from tools.translate import _ |
122 | + |
123 | +class procurement_list(osv.osv): |
124 | + _name = 'procurement.list' |
125 | + _description = 'Procurement list' |
126 | + |
127 | + _columns = { |
128 | + 'name': fields.char(size=64, string='Ref.', required=True, readonly=True, |
129 | + states={'draft': [('readonly', False)]}), |
130 | + 'requestor': fields.char(size=20, string='Requestor',), |
131 | + 'order_date': fields.date(string='Order date', required=True), |
132 | + 'warehouse_id': fields.many2one('stock.warehouse', string='Warehouse'), |
133 | + 'origin': fields.char(size=64, string='Origin'), |
134 | + 'state': fields.selection([('draft', 'Draft'),('done', 'Done'), ('cancel', 'Cancel')], |
135 | + string='State', readonly=True), |
136 | + 'line_ids': fields.one2many('procurement.list.line', 'list_id', string='Lines', readonly=True, |
137 | + states={'draft': [('readonly', False)]}), |
138 | + 'notes': fields.text(string='Notes'), |
139 | + 'supplier_ids': fields.many2many('res.partner', 'procurement_list_supplier_rel', |
140 | + 'list_id', 'supplier_id', string='Suppliers', |
141 | + domain="[('supplier', '=', True)]", |
142 | + states={'done': [('readonly', True)]}), |
143 | + 'order_ids': fields.many2many('purchase.order', 'procurement_list_order_rel', |
144 | + 'list_id', 'order_id', string='Orders', readonly=True), |
145 | + } |
146 | + |
147 | + _defaults = { |
148 | + 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'procurement.list'), |
149 | + 'state': lambda *a: 'draft', |
150 | + 'order_date': lambda *a: time.strftime('%Y-%m-%d'), |
151 | + } |
152 | + |
153 | + def copy(self, cr, uid, ids, default={}, context={}): |
154 | + ''' |
155 | + Increments the sequence for the new list |
156 | + ''' |
157 | + default['name'] = self.pool.get('ir.sequence').get(cr, uid, 'procurement.list') |
158 | + |
159 | + res = super(procurement_list, self).copy(cr, uid, ids, default, context=context) |
160 | + |
161 | + return res |
162 | + |
163 | + def cancel(self, cr, uid, ids, context={}): |
164 | + ''' |
165 | + Sets the procurement list to the 'Cancel' state |
166 | + ''' |
167 | + self.write(cr, uid, ids, {'state': 'cancel'}) |
168 | + |
169 | + return True |
170 | + |
171 | + def create_rfq(self, cr, uid, ids, context={}): |
172 | + ''' |
173 | + Create a RfQ per supplier with all products |
174 | + ''' |
175 | + purchase_obj = self.pool.get('purchase.order') |
176 | + line_obj = self.pool.get('purchase.order.line') |
177 | + |
178 | + order_ids = [] |
179 | + |
180 | + for list in self.browse(cr, uid, ids, context=context): |
181 | + # Returns an error message if no suppliers or no products |
182 | + if not list.supplier_ids or len(list.supplier_ids) == 0: |
183 | + raise osv.except_osv(_('Error'), _('No supplier defined for this list !')) |
184 | + if not list.line_ids or len(list.line_ids) == 0: |
185 | + raise osv.except_osv(_('Error'), _('No line defined for this list !')) |
186 | + |
187 | + location_id = self._get_location(cr, uid, list.warehouse_id) |
188 | + # Creates a RfQ for each supplier... |
189 | + for supplier in list.supplier_ids: |
190 | + po_id = purchase_obj.create(cr, uid, {'partner_id': supplier.id, |
191 | + 'partner_address_id': supplier.address_get().get('default'), |
192 | + 'pricelist_id': supplier.property_product_pricelist.id, |
193 | + 'origin': list.name, |
194 | + 'location_id': location_id}) |
195 | + order_ids.append(po_id) |
196 | + |
197 | + # ... with all lines... |
198 | + for line in list.line_ids: |
199 | + # ... which aren't from stock |
200 | + if not line.from_stock: |
201 | + line_obj.create(cr, uid, {'product_uom': line.product_uom_id.id, |
202 | + 'product_id': line.product_id.id, |
203 | + 'order_id': po_id, |
204 | + 'price_unit': 0.00, |
205 | + 'date_planned': list.order_date, |
206 | + 'product_qty': line.product_qty, |
207 | + 'procurement_line_id': line.id, |
208 | + 'name': line.product_id.name,}) |
209 | + self.pool.get('procurement.list.line').write(cr, uid, line.id, {'latest': 'RfQ In Progress'}) |
210 | + |
211 | + self.write(cr, uid, ids, {'state': 'done', 'order_ids': [(6, 0, order_ids)]}) |
212 | + |
213 | + return {'type': 'ir.actions.act_window', |
214 | + 'res_model': 'purchase.order', |
215 | + 'view_type': 'form', |
216 | + 'view_mode': 'tree,form', |
217 | + 'domain': [('id', 'in', order_ids)]} |
218 | + |
219 | + def reset(self, cr, uid, ids, context={}): |
220 | + ''' |
221 | + Sets the procurement list to the 'Draft' state |
222 | + ''' |
223 | + self.write(cr, uid, ids, {'state': 'draft'}) |
224 | + |
225 | + return True |
226 | + |
227 | + def _get_location(self, cr, uid, warehouse=None): |
228 | + ''' |
229 | + Returns the default input location for product |
230 | + ''' |
231 | + if warehouse: |
232 | + return warehouse.lot_input_id.id |
233 | + warehouse_obj = self.pool.get('stock.warehouse') |
234 | + warehouse_id = warehouse_obj.search(cr, uid, [])[0] |
235 | + return warehouse_obj.browse(cr, uid, warehouse_id).lot_input_id.id |
236 | + |
237 | +procurement_list() |
238 | + |
239 | + |
240 | +class procurement_list_line(osv.osv): |
241 | + _name = 'procurement.list.line' |
242 | + _description = 'Procurement line' |
243 | + _rec_name = 'product_id' |
244 | + |
245 | + _columns = { |
246 | + 'product_id': fields.many2one('product.product', string='Product', required=True), |
247 | + 'product_uom_id': fields.many2one('product.uom', string='UoM', required=True), |
248 | + 'product_qty': fields.float(digits=(16,4), string='Quantity', required=True), |
249 | + 'comment': fields.char(size=128, string='Comment'), |
250 | + 'from_stock': fields.boolean(string='From stock ?'), |
251 | + 'latest': fields.char(size=64, string='Latest document', readonly=True), |
252 | + 'list_id': fields.many2one('procurement.list', string='List', required=True, ondelete='cascade'), |
253 | + } |
254 | + |
255 | + _defaults = { |
256 | + 'latest': lambda *a: '', |
257 | + } |
258 | + |
259 | + def copy_data(self, cr, uid, id, default={}, context={}): |
260 | + ''' |
261 | + Initializes the 'latest' fields to an empty field |
262 | + ''' |
263 | + default['latest'] = '' |
264 | + |
265 | + res = super(procurement_list_line, self).copy_data(cr, uid, id, default, context=context) |
266 | + |
267 | + return res |
268 | + |
269 | + def product_id_change(self, cr, uid, ids, product_id, context={}): |
270 | + ''' |
271 | + Fills automatically the product_uom_id field on the line when the |
272 | + product was changed. |
273 | + ''' |
274 | + product_obj = self.pool.get('product.product') |
275 | + |
276 | + v = {} |
277 | + if not product_id: |
278 | + v.update({'product_uom_id': False}) |
279 | + else: |
280 | + product = product_obj.browse(cr, uid, product_id, context=context) |
281 | + v.update({'product_uom_id': product.uom_id.id}) |
282 | + |
283 | + return {'value': v} |
284 | + |
285 | +procurement_list_line() |
286 | + |
287 | + |
288 | +class purchase_order_line(osv.osv): |
289 | + _name = 'purchase.order.line' |
290 | + _inherit = 'purchase.order.line' |
291 | + |
292 | + _columns = { |
293 | + 'procurement_line_id': fields.many2one('procurement.list.line', string='Procurement Line', readonly=True, ondelete='set null'), |
294 | + } |
295 | + |
296 | + def action_confirm(self, cr, uid, ids, context={}): |
297 | + ''' |
298 | + Changes the status of the procurement line |
299 | + ''' |
300 | + proc_line_obj = self.pool.get('procurement.list.line') |
301 | + for line in self.browse(cr, uid, ids): |
302 | + if line.procurement_line_id and line.procurement_line_id.id: |
303 | + proc_line_obj.write(cr, uid, [line.procurement_line_id.id], {'latest': line.order_id.name}) |
304 | + |
305 | + return super(purchase_order_line, self).action_confirm(cr, uid, ids, context=context) |
306 | + |
307 | +purchase_order_line() |
308 | + |
309 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
310 | + |
311 | |
312 | === added file 'procurement_list/procurement_list_sequence.xml' |
313 | --- procurement_list/procurement_list_sequence.xml 1970-01-01 00:00:00 +0000 |
314 | +++ procurement_list/procurement_list_sequence.xml 2011-03-16 16:41:25 +0000 |
315 | @@ -0,0 +1,20 @@ |
316 | +<?xml version="1.0" encoding="utf-8" ?> |
317 | +<openerp> |
318 | + <data> |
319 | + |
320 | + <!-- Sequences for procurement.list --> |
321 | + <record id="seq_type_procurement_list" model="ir.sequence.type"> |
322 | + <field name="name">Procurement List Order</field> |
323 | + <field name="code">procurement.list</field> |
324 | + </record> |
325 | + |
326 | + <record id="seq_procurement_list" model="ir.sequence"> |
327 | + <field name="name">Procurement List</field> |
328 | + <field name="code">procurement.list</field> |
329 | + <field name="prefix">PR/</field> |
330 | + <field name="padding">5</field> |
331 | + </record> |
332 | + |
333 | + </data> |
334 | +</openerp> |
335 | + |
336 | |
337 | === added file 'procurement_list/procurement_list_view.xml' |
338 | --- procurement_list/procurement_list_view.xml 1970-01-01 00:00:00 +0000 |
339 | +++ procurement_list/procurement_list_view.xml 2011-03-16 16:41:25 +0000 |
340 | @@ -0,0 +1,111 @@ |
341 | +<?xml version="1.0" encoding="utf-8" ?> |
342 | +<openerp> |
343 | + <data> |
344 | + |
345 | + <record id="procurement_list_form_view" model="ir.ui.view"> |
346 | + <field name="name">procurement.list.form.view</field> |
347 | + <field name="model">procurement.list</field> |
348 | + <field name="type">form</field> |
349 | + <field name="arch" type="xml"> |
350 | + <form string="Procurement list"> |
351 | + <group colspan="4" col="6"> |
352 | + <field name="name" /> |
353 | + <field name="order_date" /> |
354 | + <field name="warehouse_id" widget="selection" /> |
355 | + <field name="requestor" /> |
356 | + <field name="origin" /> |
357 | + </group> |
358 | + <notebook colspan="4"> |
359 | + <page string="Products"> |
360 | + <field name="line_ids" mode="tree" colspan="4" nolabel="1"> |
361 | + <tree editable="top" string="Products"> |
362 | + <field name="product_id" on_change="product_id_change(product_id)" /> |
363 | + <field name="product_uom_id" /> |
364 | + <field name="product_qty" /> |
365 | + <field name="comment" /> |
366 | + <field name="from_stock" /> |
367 | + <field name="latest" /> |
368 | + </tree> |
369 | + </field> |
370 | + <group colspan="4" col="6"> |
371 | + <field name="state" /> |
372 | + <button name="cancel" string="Cancel" icon="gtk-cancel" type="object" states="draft"/> |
373 | + <button name="create_rfq" string="Create RfQ" icon="gtk-execute" type="object" states="draft" /> |
374 | + <button name="reset" string="Reset to Draft" icon="gtk-ok" type="object" states="cancel" /> |
375 | + </group> |
376 | + </page> |
377 | + <page string="Suppliers"> |
378 | + <field name="supplier_ids" nolabel="1" /> |
379 | + </page> |
380 | + <page string="Sourcing Documents"> |
381 | + <field name="order_ids" nolabel="1" colspan="4"> |
382 | + <tree string="Sourcing Documents" colors="blue:state=='draft'"> |
383 | + <field name="name" /> |
384 | + <field name="partner_id" /> |
385 | + <field name="state" /> |
386 | + </tree> |
387 | + </field> |
388 | + </page> |
389 | + <page string="Notes"> |
390 | + <field name="notes" nolabel="1" /> |
391 | + </page> |
392 | + </notebook> |
393 | + </form> |
394 | + </field> |
395 | + </record> |
396 | + |
397 | + <record id="procurement_list_tree_view" model="ir.ui.view"> |
398 | + <field name="name">procurement.list.tree.view</field> |
399 | + <field name="model">procurement.list</field> |
400 | + <field name="type">tree</field> |
401 | + <field name="arch" type="xml"> |
402 | + <tree string="Procurement lists"> |
403 | + <field name="name" /> |
404 | + <field name="order_date" /> |
405 | + <field name="requestor" /> |
406 | + </tree> |
407 | + </field> |
408 | + </record> |
409 | + |
410 | + <record id="procurement_list_filter_view" model="ir.ui.view"> |
411 | + <field name="name">procurement.list.filter.view</field> |
412 | + <field name="model">procurement.list</field> |
413 | + <field name="type">search</field> |
414 | + <field name="arch" type="xml"> |
415 | + <search string="Search Procurement List"> |
416 | + <group col="10" colspan="4"> |
417 | + <filter icon="terp-document-new" string="Draft" domain="[('state', '=', 'draft')]" help="Draft Procurement list" /> |
418 | + <filter icon="terp-dialog-close" string="Done" domain="[('state', '=', 'done')]" help="Done Procurement list" /> |
419 | + <separator orientation="vertical" /> |
420 | + <field name="name" /> |
421 | + <field name="order_date" /> |
422 | + <field name="requestor" /> |
423 | + </group> |
424 | + <newline /> |
425 | + <group expand="0" string="Group By..." colspan="4" col="10" groups="base.group_extended"> |
426 | + <filter string="Responsible" icon="terp-personal" domain="[]" context="{'group_by': 'user_id'}" /> |
427 | + <separator orientation="vertical" /> |
428 | + </group> |
429 | + </search> |
430 | + </field> |
431 | + </record> |
432 | + |
433 | + <record id="action_procurement_list_tree" model="ir.actions.act_window"> |
434 | + <field name="name">Procurement Lists</field> |
435 | + <field name="res_model">procurement.list</field> |
436 | + <field name="view_type">form</field> |
437 | + <field name="view_mode">tree,form</field> |
438 | + <field name="search_id" ref="procurement_list_filter_view" /> |
439 | + <field name="help">A procurement list is a list of items to supply. After creation of the list of items, you can choose |
440 | + a list of suppliers and create automatically a Request for Quotation for each supplier.</field> |
441 | + </record> |
442 | + |
443 | + <menuitem |
444 | + action="action_procurement_list_tree" |
445 | + id="menu_procurement_list" |
446 | + sequence="0" |
447 | + parent="purchase.menu_procurement_management" /> |
448 | + |
449 | + </data> |
450 | +</openerp> |
451 | + |
452 | |
453 | === added file 'procurement_list/procurement_list_wizard.xml' |
454 | --- procurement_list/procurement_list_wizard.xml 1970-01-01 00:00:00 +0000 |
455 | +++ procurement_list/procurement_list_wizard.xml 2011-03-16 16:41:25 +0000 |
456 | @@ -0,0 +1,21 @@ |
457 | +<?xml version="1.0" encoding="utf-8" ?> |
458 | +<openerp> |
459 | + <data> |
460 | + |
461 | + <wizard |
462 | + id="wizard_list_to_order" |
463 | + model="procurement.list" |
464 | + name="wizard_list_to_order" |
465 | + keyword="client_action_relate" |
466 | + string="Purchase Orders" /> |
467 | + |
468 | + <wizard |
469 | + id="wizard_list_to_rfq" |
470 | + model="procurement.list" |
471 | + name="wizard_list_to_rfq" |
472 | + keyword="client_action_relate" |
473 | + string="Requests for Quotation" /> |
474 | + |
475 | + </data> |
476 | +</openerp> |
477 | + |
478 | |
479 | === added directory 'procurement_list/security' |
480 | === added file 'procurement_list/security/ir.model.access.csv' |
481 | --- procurement_list/security/ir.model.access.csv 1970-01-01 00:00:00 +0000 |
482 | +++ procurement_list/security/ir.model.access.csv 2011-03-16 16:41:25 +0000 |
483 | @@ -0,0 +1,3 @@ |
484 | +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" |
485 | +"access_procurement_list_all","procurement.list all","model_procurement_list",,1,1,1,1 |
486 | +"access_procurement_list_line_all","procurement.list.line all","model_procurement_list_line",,1,1,1,1 |
487 | |
488 | === added directory 'procurement_list/test' |
489 | === added file 'procurement_list/test/list_import.csv' |
490 | --- procurement_list/test/list_import.csv 1970-01-01 00:00:00 +0000 |
491 | +++ procurement_list/test/list_import.csv 2011-03-16 16:41:25 +0000 |
492 | @@ -0,0 +1,5 @@ |
493 | +'CPU1';'Processor AMD Athlon XP 1800+';'PCE';12,00;'test import'; |
494 | +'CPU2';'High speed processor config';'PCE';15,00;;'True' |
495 | +'CPU3';'Processor AMD Athlon XP 2200+';'PCE';25,00;; |
496 | +'CPU_DEM';'Processor on demand';'PCE';30,00;; |
497 | +'CPU_GEN';'Regular processor config';'PCE';32,00;; |
498 | |
499 | === added file 'procurement_list/test/procurement_list.yml' |
500 | --- procurement_list/test/procurement_list.yml 1970-01-01 00:00:00 +0000 |
501 | +++ procurement_list/test/procurement_list.yml 2011-03-16 16:41:25 +0000 |
502 | @@ -0,0 +1,143 @@ |
503 | +- |
504 | + In order to test the procurement list module, I start by creating a new product category |
505 | +- |
506 | + !record {model: product.category, id: product_cat1}: |
507 | + name: Categ1 |
508 | +- |
509 | + I create new product 'P1' |
510 | +- |
511 | + !record {model: product.product, id: product1}: |
512 | + categ_id: product_cat1 |
513 | + cost_method: standard |
514 | + mes_type: fixed |
515 | + name: P1 |
516 | + price_margin: 2.0 |
517 | + procure_method: make_to_stock |
518 | + property_stock_inventory: stock.location_inventory |
519 | + property_stock_procurement: stock.location_procurement |
520 | + property_stock_production: stock.location_production |
521 | + seller_delay: '1' |
522 | + standard_price: 100.0 |
523 | + supply_method: buy |
524 | + type: product |
525 | + uom_id: product.product_uom_unit |
526 | + uom_po_id: product.product_uom_unit |
527 | + volume: 0.0 |
528 | + warranty: 0.0 |
529 | + weight: 0.0 |
530 | + weight_net: 0.0 |
531 | +- |
532 | + I create a second product, P2 |
533 | +- |
534 | + !record {model: product.product, id: product2}: |
535 | + categ_id: product_cat1 |
536 | + cost_method: standard |
537 | + mes_type: fixed |
538 | + name: P2 |
539 | + price_margin: 2.0 |
540 | + procure_method: make_to_stock |
541 | + property_stock_inventory: stock.location_inventory |
542 | + property_stock_procurement: stock.location_procurement |
543 | + property_stock_production: stock.location_production |
544 | + seller_delay: '1' |
545 | + standard_price: 100.0 |
546 | + supply_method: buy |
547 | + type: product |
548 | + uom_id: product.product_uom_unit |
549 | + uom_po_id: product.product_uom_unit |
550 | + volume: 0.0 |
551 | + warranty: 0.0 |
552 | + weight: 0.0 |
553 | + weight_net: 0.0 |
554 | +- |
555 | + I create a partner |
556 | +- |
557 | + !record {model: res.partner, id: partner1}: |
558 | + name: Supplier 1 |
559 | +- |
560 | + I create an address for this partner |
561 | +- |
562 | + !record {model: res.partner.address, id: addr1}: |
563 | + partner_id: partner1 |
564 | + name: Supplier1 |
565 | +- |
566 | + I create a second partner |
567 | +- |
568 | + !record {model: res.partner, id: partner2}: |
569 | + name: Supplier 2 |
570 | +- |
571 | + I create an address for this second partner |
572 | +- |
573 | + !record {model: res.partner.address, id: addr2}: |
574 | + partner_id: partner2 |
575 | + name: Supplier2 |
576 | +- |
577 | + I create a procurement list for these two products |
578 | +- |
579 | + !record {model: procurement.list, id: list1}: |
580 | + requestor: Administration |
581 | + warehouse_id: stock.warehouse0 |
582 | + origin: R 20043 |
583 | + supplier_ids: |
584 | + - partner1 |
585 | + - partner2 |
586 | +- |
587 | + I add two lines in this procurement list |
588 | +- |
589 | + !record {model: procurement.list.line, id: list1_line1}: |
590 | + product_id: product1 |
591 | + product_uom_id: product.product_uom_unit |
592 | + product_qty: 100.00 |
593 | + comment: special comment |
594 | + from_stock: False |
595 | + list_id: list1 |
596 | +- |
597 | + The second line |
598 | +- |
599 | + !record {model: procurement.list.line, id: list1_line2}: |
600 | + product_id: product2 |
601 | + product_uom_id: product.product_uom_unit |
602 | + product_qty: 50.0 |
603 | + from_stock: True |
604 | + list_id: list1 |
605 | +- |
606 | + I check that the list which was initially in the draft state |
607 | +- |
608 | + !assert {model: procurement.list, id: list1}: |
609 | + - state == 'draft' |
610 | + - requestor == 'Administration' |
611 | + - origin == 'R 20043' |
612 | +- |
613 | + I create RfQ from this list |
614 | +- |
615 | + !python {model: procurement.list}: | |
616 | + pl_obj = self.pool.get('procurement.list') |
617 | + pl_id1 = pl_obj.browse(cr, uid, ref('list1')) |
618 | + pl_id1.create_rfq() |
619 | +- |
620 | + I check if the latest message in procurement line is 'RfQ In Progress' |
621 | +- |
622 | + !assert {model: procurement.list.line, id: list1_line1}: |
623 | + - latest == 'RfQ In Progress' |
624 | +- |
625 | + I check if only one RfQ will be created for the |
626 | +- |
627 | + !python {model: procurement.list}: | |
628 | + pl_obj = self.pool.get('procurement.list') |
629 | + pol_obj = self.pool.get('purchase.order.line') |
630 | + order_obj = self.pool.get('purchase.order') |
631 | + pol_ids = pol_obj.search(cr, uid, [('product_id', '=', ref('product1'))]) |
632 | + pl = pl_obj.browse(cr, uid, ref('list1')) |
633 | + |
634 | + for pol in pol_obj.browse(cr, uid, pol_ids): |
635 | + assert pol.order_id.origin == pl.name, "Bad name for the name of the generated Purchase Order" |
636 | + order_obj.wkf_confirm_order(cr, uid, [pol.order_id.id]) |
637 | + assert pol.procurement_line_id.latest == pol.order_id.name, "Procurement line not updated" |
638 | +- |
639 | + I check that no purchase order line with P2 |
640 | +- |
641 | + !python {model: procurement.list}: | |
642 | + pl_obj = self.pool.get('procurement.list') |
643 | + pol_obj = self.pool.get('purchase.order.line') |
644 | + pol_ids = pol_obj.search(cr, uid, [('product_id', '=', ref('product2'))]) |
645 | + assert len(pol_ids) == 0, "No purchase order line found !" |
646 | |
647 | === added directory 'procurement_list/wizard' |
648 | === added file 'procurement_list/wizard/__init__.py' |
649 | --- procurement_list/wizard/__init__.py 1970-01-01 00:00:00 +0000 |
650 | +++ procurement_list/wizard/__init__.py 2011-03-16 16:41:25 +0000 |
651 | @@ -0,0 +1,28 @@ |
652 | +#!/usr/bin/env python |
653 | +# -*- encoding: utf-8 -*- |
654 | +############################################################################## |
655 | +# |
656 | +# OpenERP, Open Source Management Solution |
657 | +# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). |
658 | +# |
659 | +# This program is free software: you can redistribute it and/or modify |
660 | +# it under the terms of the GNU Affero General Public License as |
661 | +# published by the Free Software Foundation, either version 3 of the |
662 | +# License, or (at your option) any later version. |
663 | +# |
664 | +# This program is distributed in the hope that it will be useful, |
665 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
666 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
667 | +# GNU Affero General Public License for more details. |
668 | +# |
669 | +# You should have received a copy of the GNU Affero General Public License |
670 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
671 | +# |
672 | +############################################################################## |
673 | + |
674 | +import wizard_list_to_order |
675 | +import wizard_list_to_rfq |
676 | +import wizard_import_list |
677 | + |
678 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
679 | + |
680 | |
681 | === added file 'procurement_list/wizard/wizard_import_list.py' |
682 | --- procurement_list/wizard/wizard_import_list.py 1970-01-01 00:00:00 +0000 |
683 | +++ procurement_list/wizard/wizard_import_list.py 2011-03-16 16:41:25 +0000 |
684 | @@ -0,0 +1,196 @@ |
685 | +#!/usr/bin/env python |
686 | +# -*- encoding: utf-8 -*- |
687 | +############################################################################## |
688 | +# |
689 | +# OpenERP, Open Source Management Solution |
690 | +# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). |
691 | +# |
692 | +# This program is free software: you can redistribute it and/or modify |
693 | +# it under the terms of the GNU Affero General Public License as |
694 | +# published by the Free Software Foundation, either version 3 of the |
695 | +# License, or (at your option) any later version. |
696 | +# |
697 | +# This program is distributed in the hope that it will be useful, |
698 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
699 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
700 | +# GNU Affero General Public License for more details. |
701 | +# |
702 | +# You should have received a copy of the GNU Affero General Public License |
703 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
704 | +# |
705 | +############################################################################## |
706 | + |
707 | +from osv import osv |
708 | +from osv import fields |
709 | + |
710 | +from tools.translate import _ |
711 | + |
712 | +from tempfile import TemporaryFile |
713 | + |
714 | +import base64 |
715 | +import csv |
716 | + |
717 | + |
718 | + |
719 | +class wizard_import_list(osv.osv_memory): |
720 | + _name = 'procurement.list.import' |
721 | + _description = 'Import of Procurement List' |
722 | + |
723 | + _columns = { |
724 | + 'file': fields.binary(strign='File to import', required=True), |
725 | + 'message': fields.text(string='Message', readonly=True), |
726 | + 'info': fields.text(string='Info', readonly=True), |
727 | + } |
728 | + |
729 | + _defaults = { |
730 | + 'info': lambda *a : """ |
731 | + The file should be in CSV format (with ';' character as delimiter). |
732 | + The columns should be in this order : |
733 | + * Product code |
734 | + * Product name |
735 | + * UoM |
736 | + * Quantity |
737 | + * Comment |
738 | + * From stock ? (Write True if you want that the product will be supply from stock, leave blank if not) |
739 | + """ |
740 | + } |
741 | + |
742 | + def close_window(self, cr, uid, ids, context={}): |
743 | + ''' |
744 | + Simply close the wizard |
745 | + ''' |
746 | + return {'type': 'ir.actions.act_window', |
747 | + 'res_model': 'procurement.list', |
748 | + 'view_type': 'form', |
749 | + 'view_mode': 'form,tree', |
750 | + 'res_id': context.get('list_id'), |
751 | + 'target': 'crush'} |
752 | + |
753 | + def default_get(self, cr, uid, fields, context={}): |
754 | + ''' |
755 | + Check if the Procurement List is saved and |
756 | + set the error message |
757 | + ''' |
758 | + res = super(wizard_import_list, self).default_get(cr, uid, fields, context=context) |
759 | + |
760 | + # If we are after the importation |
761 | + if context.get('step', False) and context.get('step', False) == 'import': |
762 | + res['message'] = context.get('message', '') |
763 | + |
764 | + return res |
765 | + else: |
766 | + if not context.get('active_id', False): |
767 | + raise osv.except_osv(_('Warning'), _('Please save the Procurement List before importing lines')) |
768 | + |
769 | + # We check if the Procurement List is in 'Draft' state |
770 | + list_id = self.pool.get('procurement.list').browse(cr, uid, context.get('active_id', [])) |
771 | + if not list_id or list_id.state != 'draft': |
772 | + raise osv.except_osv(_('Warning'), _('You cannot import lines in a confirmed Procurement List')) |
773 | + |
774 | + return res |
775 | + |
776 | + |
777 | + def import_file(self, cr, uid, ids, context={}): |
778 | + ''' |
779 | + Import the file passed on the wizard in the |
780 | + Procurement List |
781 | + ''' |
782 | + list_line_obj = self.pool.get('procurement.list.line') |
783 | + list_obj = self.pool.get('procurement.list') |
784 | + product_obj = self.pool.get('product.product') |
785 | + uom_obj = self.pool.get('product.uom') |
786 | + model_data_obj = self.pool.get('ir.model.data') |
787 | + |
788 | + list_id = context.get('active_id', False) |
789 | + |
790 | + if not list_id: |
791 | + raise osv.except_osv(_('Error'), _('The system hasn\'t found a Procurement List to import lines')) |
792 | + |
793 | + import_list = self.browse(cr, uid, ids[0], context) |
794 | + |
795 | + fileobj = TemporaryFile('w+') |
796 | + fileobj.write(base64.decodestring(import_list.file)) |
797 | + |
798 | + # now we determine the file format |
799 | + fileobj.seek(0) |
800 | + |
801 | + reader = csv.reader(fileobj, quotechar='\'', delimiter=';') |
802 | + |
803 | + error = '' |
804 | + |
805 | + line_num = 0 |
806 | + |
807 | + for line in reader: |
808 | + line_num += 1 |
809 | + if len(line) < 6: |
810 | + error += 'Line %s is not valid !' % (line_num) |
811 | + error += '\n' |
812 | + continue |
813 | + # Get the product |
814 | + product_ids = product_obj.search(cr, uid, [('default_code', '=', line[0])], context=context) |
815 | + if not product_ids: |
816 | + product_ids = product_obj.search(cr, uid, [('name', '=', line[1])], context=context) |
817 | + |
818 | + if not product_ids: |
819 | + error += 'Product [%s] %s not found !' % (line[0], line[1]) |
820 | + error += '\n' |
821 | + continue |
822 | + |
823 | + product_id = product_ids[0] |
824 | + |
825 | + # Get the UoM |
826 | + uom_ids = uom_obj.search(cr, uid, [('name', '=', line[2])]) |
827 | + if not uom_ids: |
828 | + error += 'Uom %s not found !' % line[2] |
829 | + error += '\n' |
830 | + continue |
831 | + |
832 | + uom_id = uom_ids[0] |
833 | + |
834 | + # Get the quantity |
835 | + product_qty = float(line[3].replace(',', '.')) |
836 | + |
837 | + # Get the comment |
838 | + comment = line[4] |
839 | + |
840 | + # Get the method |
841 | + from_stock = False |
842 | + if line[5] == 'True': |
843 | + from_stock = True |
844 | + |
845 | + |
846 | + list_line_obj.create(cr, uid, {'product_id': product_id, |
847 | + 'product_uom_id': uom_id, |
848 | + 'product_qty': product_qty, |
849 | + 'comment': comment, |
850 | + 'from_stock': from_stock, |
851 | + 'list_id': list_id}) |
852 | + |
853 | + view_ids = model_data_obj.search(cr, uid, |
854 | + [('module', '=', 'procurement_list'), |
855 | + ('name', '=', 'wizard_import_list_done')], |
856 | + offset=0, limit=1)[0] |
857 | + view_id = model_data_obj.browse(cr, uid, view_ids).res_id |
858 | + |
859 | + if error and error != '': |
860 | + context['message'] = error |
861 | + else: |
862 | + context['message'] = 'All lines have been succesfully imported !' |
863 | + |
864 | + context['step'] = 'import' |
865 | + context['list_id'] = list_id |
866 | + |
867 | + return {'type': 'ir.actions.act_window', |
868 | + 'res_model': 'procurement.list.import', |
869 | + 'view_type': 'form', |
870 | + 'view_mode': 'form', |
871 | + 'target': 'new', |
872 | + 'view_id': [view_id], |
873 | + 'context': context, |
874 | + } |
875 | + |
876 | +wizard_import_list() |
877 | + |
878 | + |
879 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
880 | + |
881 | |
882 | === added file 'procurement_list/wizard/wizard_import_list_view.xml' |
883 | --- procurement_list/wizard/wizard_import_list_view.xml 1970-01-01 00:00:00 +0000 |
884 | +++ procurement_list/wizard/wizard_import_list_view.xml 2011-03-16 16:41:25 +0000 |
885 | @@ -0,0 +1,48 @@ |
886 | +<?xml version="1.0" encoding="utf-8" ?> |
887 | +<openerp> |
888 | + <data> |
889 | + |
890 | + |
891 | + <record id="wizard_import_list_init" model="ir.ui.view"> |
892 | + <field name="name">wizard.import.list.init</field> |
893 | + <field name="model">procurement.list.import</field> |
894 | + <field name="type">form</field> |
895 | + <field name="arch" type="xml"> |
896 | + <form string="Import lines"> |
897 | + <separator string="Information" colspan="4" /> |
898 | + <field name="info" colspan="4" nolabel="1" /> |
899 | + <separator string="File to import" colspan="4" /> |
900 | + <field name="file" nolabel="1" colspan="4" /> |
901 | + <button special="cancel" string="Cancel" icon="gtk-cancel" /> |
902 | + <button name="import_file" string="Import file" type="object" icon="gtk-convert" /> |
903 | + </form> |
904 | + </field> |
905 | + </record> |
906 | + |
907 | + <record id="wizard_import_list_done" model="ir.ui.view"> |
908 | + <field name="name">wizard.import.list.done</field> |
909 | + <field name="model">procurement.list.import</field> |
910 | + <field name="type">form</field> |
911 | + <field name="arch" type="xml"> |
912 | + <form string="Result of importation"> |
913 | + <separator colspan="4" string="Result of importation" /> |
914 | + <field name="message" colspan="4" nolabel="1" /> |
915 | + <button name="close_window" string="Close" icon="gtk-close" type="object" /> |
916 | + </form> |
917 | + </field> |
918 | + </record> |
919 | + |
920 | + <act_window |
921 | + name="Import lines" |
922 | + res_model="procurement.list.import" |
923 | + src_model="procurement.list" |
924 | + view_mode="form" |
925 | + view_type="form" |
926 | + view_id="wizard_import_list_init" |
927 | + target="new" |
928 | + key2="client_action_multi" |
929 | + id="action_open_wizard_import" /> |
930 | + |
931 | + </data> |
932 | +</openerp> |
933 | + |
934 | |
935 | === added file 'procurement_list/wizard/wizard_list_to_order.py' |
936 | --- procurement_list/wizard/wizard_list_to_order.py 1970-01-01 00:00:00 +0000 |
937 | +++ procurement_list/wizard/wizard_list_to_order.py 2011-03-16 16:41:25 +0000 |
938 | @@ -0,0 +1,63 @@ |
939 | +#!/usr/bin/env python |
940 | +# -*- encoding: utf-8 -*- |
941 | +############################################################################## |
942 | +# |
943 | +# OpenERP, Open Source Management Solution |
944 | +# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). |
945 | +# |
946 | +# This program is free software: you can redistribute it and/or modify |
947 | +# it under the terms of the GNU Affero General Public License as |
948 | +# published by the Free Software Foundation, either version 3 of the |
949 | +# License, or (at your option) any later version. |
950 | +# |
951 | +# This program is distributed in the hope that it will be useful, |
952 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
953 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
954 | +# GNU Affero General Public License for more details. |
955 | +# |
956 | +# You should have received a copy of the GNU Affero General Public License |
957 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
958 | +# |
959 | +############################################################################## |
960 | + |
961 | +import wizard |
962 | +import pooler |
963 | + |
964 | +class wizard_list_to_order(wizard.interface): |
965 | + |
966 | + def _get_order(self, cr, uid, data, context={}): |
967 | + ''' |
968 | + Returns the Purchase Orders generated by the current |
969 | + Procurement List |
970 | + ''' |
971 | + pool_obj = pooler.get_pool(cr.dbname) |
972 | + list_obj = pool_obj.get('procurement.list') |
973 | + |
974 | + order_ids = [] |
975 | + |
976 | + for l in list_obj.browse(cr, uid, data['ids'], context=context): |
977 | + for o in l.order_ids: |
978 | + order_ids.append(o.id) |
979 | + |
980 | + return {'type': 'ir.actions.act_window', |
981 | + 'res_model': 'purchase.order', |
982 | + 'view_type': 'form', |
983 | + 'view_mode': 'tree,form', |
984 | + 'domain': [('state', '!=', 'draft'), ('id', 'in', order_ids)], |
985 | + } |
986 | + |
987 | + states = { |
988 | + 'init': { |
989 | + 'actions': [], |
990 | + 'result': { |
991 | + 'type': 'action', |
992 | + 'action': _get_order, |
993 | + 'state': 'end', |
994 | + }, |
995 | + }, |
996 | + } |
997 | + |
998 | +wizard_list_to_order('wizard_list_to_order') |
999 | + |
1000 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
1001 | + |
1002 | |
1003 | === added file 'procurement_list/wizard/wizard_list_to_rfq.py' |
1004 | --- procurement_list/wizard/wizard_list_to_rfq.py 1970-01-01 00:00:00 +0000 |
1005 | +++ procurement_list/wizard/wizard_list_to_rfq.py 2011-03-16 16:41:25 +0000 |
1006 | @@ -0,0 +1,63 @@ |
1007 | +#!/usr/bin/env python |
1008 | +# -*- encoding: utf-8 -*- |
1009 | +############################################################################## |
1010 | +# |
1011 | +# OpenERP, Open Source Management Solution |
1012 | +# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). |
1013 | +# |
1014 | +# This program is free software: you can redistribute it and/or modify |
1015 | +# it under the terms of the GNU Affero General Public License as |
1016 | +# published by the Free Software Foundation, either version 3 of the |
1017 | +# License, or (at your option) any later version. |
1018 | +# |
1019 | +# This program is distributed in the hope that it will be useful, |
1020 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1021 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1022 | +# GNU Affero General Public License for more details. |
1023 | +# |
1024 | +# You should have received a copy of the GNU Affero General Public License |
1025 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
1026 | +# |
1027 | +############################################################################## |
1028 | + |
1029 | +import wizard |
1030 | +import pooler |
1031 | + |
1032 | +class wizard_list_to_rfq(wizard.interface): |
1033 | + |
1034 | + def _get_order(self, cr, uid, data, context={}): |
1035 | + ''' |
1036 | + Returns the Requests for Quotation generated by the current |
1037 | + Procurement List |
1038 | + ''' |
1039 | + pool_obj = pooler.get_pool(cr.dbname) |
1040 | + list_obj = pool_obj.get('procurement.list') |
1041 | + |
1042 | + order_ids = [] |
1043 | + |
1044 | + for l in list_obj.browse(cr, uid, data['ids'], context=context): |
1045 | + for o in l.order_ids: |
1046 | + order_ids.append(o.id) |
1047 | + |
1048 | + return {'type': 'ir.actions.act_window', |
1049 | + 'res_model': 'purchase.order', |
1050 | + 'view_type': 'form', |
1051 | + 'view_mode': 'tree,form', |
1052 | + 'domain': [('state', '=', 'draft'), ('id', 'in', order_ids)], |
1053 | + } |
1054 | + |
1055 | + states = { |
1056 | + 'init': { |
1057 | + 'actions': [], |
1058 | + 'result': { |
1059 | + 'type': 'action', |
1060 | + 'action': _get_order, |
1061 | + 'state': 'end', |
1062 | + }, |
1063 | + }, |
1064 | + } |
1065 | + |
1066 | +wizard_list_to_rfq('wizard_list_to_rfq') |
1067 | + |
1068 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
1069 | + |