Merge lp:~unifield-team/unifield-addons/new-purchase-order-state into lp:unifield-addons

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
Reviewer Review Type Date Requested Status
UniField Dev Team Pending
Review via email: mp+53577@code.launchpad.net
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+

Subscribers

People subscribed via source and target branches

to all changes: