Merge lp:~camptocamp/openerp-humanitarian-ngo/add-bid_selected_state_on_po-nbi into lp:openerp-humanitarian-ngo/purchase-wkfl
- add-bid_selected_state_on_po-nbi
- Merge into purchase-wkfl
Proposed by
Nicolas Bessi - Camptocamp
Status: | Merged |
---|---|
Approved by: | Yannick Vaucher @ Camptocamp |
Approved revision: | 79 |
Merged at revision: | 73 |
Proposed branch: | lp:~camptocamp/openerp-humanitarian-ngo/add-bid_selected_state_on_po-nbi |
Merge into: | lp:openerp-humanitarian-ngo/purchase-wkfl |
Diff against target: |
253 lines (+96/-25) 6 files modified
purchase_extended/model/purchase_order.py (+8/-2) purchase_extended/test/process/bid2order.yml (+8/-3) purchase_extended/workflow/purchase_order.xml (+14/-0) purchase_requisition_extended/model/purchase_order.py (+2/-0) purchase_requisition_extended/model/purchase_requisition.py (+63/-20) purchase_requisition_extended/view/purchase_requisition.xml (+1/-0) |
To merge this branch: | bzr merge lp:~camptocamp/openerp-humanitarian-ngo/add-bid_selected_state_on_po-nbi |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Yannick Vaucher @ Camptocamp | code review, no tests | Approve | |
Romain Deheele - Camptocamp (community) | code review | Approve | |
Joël Grand-Guillaume @ camptocamp | code review + test | Approve | |
Review via email: mp+200799@code.launchpad.net |
Commit message
[ADD] a bid selected state on PO
[FIX] confirmation of cost estimate. PO in state bid will be cancel too
Description of the change
Add a bid selected state on PO
Fix confirmation of cost estimate. PO in state bid will be cancel too
To post a comment you must log in.
Revision history for this message
Nicolas Bessi - Camptocamp (nbessi-c2c-deactivatedaccount) wrote : | # |
- 74. By Nicolas Bessi <email address hidden>
-
[FIX] on change product line on po line to allows manual creation of purchase order
- 75. By Nicolas Bessi <email address hidden>
-
[FIX] test do not use draftbid state but sent state as we can not send context to the server
- 76. By Joël Grand-Guillaume @ camptocamp
-
[ADD] The pricelist on the requisition and bid creation process
- 77. By Joël Grand-Guillaume @ camptocamp
-
[IMP] Add a pricelist in tendering process
- 78. By Nicolas Bessi - Camptocamp
-
[MRG] from head
Revision history for this message
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote : | # |
LGTM, Thanks !
review:
Approve
(code review + test)
Revision history for this message
Romain Deheele - Camptocamp (romaindeheele) wrote : | # |
LGTM,
Romain
review:
Approve
(code review)
- 79. By Yannick Vaucher @ Camptocamp
-
[IMP] assign single dict value using key instead of using update
Revision history for this message
Yannick Vaucher @ Camptocamp (yvaucher-c2c) wrote : | # |
LGTM
review:
Approve
(code review, no tests)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'purchase_extended/model/purchase_order.py' | |||
2 | --- purchase_extended/model/purchase_order.py 2013-11-13 08:49:37 +0000 | |||
3 | +++ purchase_extended/model/purchase_order.py 2014-02-06 13:54:03 +0000 | |||
4 | @@ -14,6 +14,7 @@ | |||
5 | 14 | ('sent', 'RFQ Sent'), | 14 | ('sent', 'RFQ Sent'), |
6 | 15 | ('draftbid', 'Draft Bid'), # added | 15 | ('draftbid', 'Draft Bid'), # added |
7 | 16 | ('bid', 'Bid Encoded'), # Bid Received renamed into Bid Encoded | 16 | ('bid', 'Bid Encoded'), # Bid Received renamed into Bid Encoded |
8 | 17 | ('bid_selected', 'Bid selected'), # added | ||
9 | 17 | ('draftpo', 'Draft PO'), # added | 18 | ('draftpo', 'Draft PO'), # added |
10 | 18 | ('confirmed', 'Waiting Approval'), | 19 | ('confirmed', 'Waiting Approval'), |
11 | 19 | ('approved', 'Purchase Confirmed'), | 20 | ('approved', 'Purchase Confirmed'), |
12 | @@ -236,17 +237,22 @@ | |||
13 | 236 | value['value']['dest_address_id'] = dest_id | 237 | value['value']['dest_address_id'] = dest_id |
14 | 237 | return value | 238 | return value |
15 | 238 | 239 | ||
16 | 240 | def po_tender_requisition_selected(self, cr, uid, ids, context=None): | ||
17 | 241 | """Workflow function that write state 'bid selected'""" | ||
18 | 242 | return self.write(cr, uid, ids, {'state': 'bid_selected'}, | ||
19 | 243 | context=context) | ||
20 | 244 | |||
21 | 239 | 245 | ||
22 | 240 | class purchase_order_line(orm.Model): | 246 | class purchase_order_line(orm.Model): |
23 | 241 | _inherit = 'purchase.order.line' | 247 | _inherit = 'purchase.order.line' |
24 | 242 | 248 | ||
25 | 243 | def onchange_product_id(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id, | 249 | def onchange_product_id(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id, |
26 | 244 | partner_id, date_order=False, fiscal_position_id=False, date_planned=False, | 250 | partner_id, date_order=False, fiscal_position_id=False, date_planned=False, |
28 | 245 | name=False, price_unit=False, context=None, state='draft', type='rfq', **kwargs): | 251 | name=False, price_unit=False, context=None, state='draftpo', type='purchase', **kwargs): |
29 | 246 | res = super(purchase_order_line, self).onchange_product_id(cr, uid, ids, | 252 | res = super(purchase_order_line, self).onchange_product_id(cr, uid, ids, |
30 | 247 | pricelist_id, product_id, qty, uom_id, partner_id, date_order, | 253 | pricelist_id, product_id, qty, uom_id, partner_id, date_order, |
31 | 248 | fiscal_position_id, date_planned, name, price_unit, context) | 254 | fiscal_position_id, date_planned, name, price_unit, context) |
33 | 249 | if state == 'draft' and type == 'bid': | 255 | if state == 'draft' and type == 'rfq': |
34 | 250 | res['value'].update({'price_unit': 0}) | 256 | res['value'].update({'price_unit': 0}) |
35 | 251 | elif state in ('sent', 'draftbid', 'bid'): | 257 | elif state in ('sent', 'draftbid', 'bid'): |
36 | 252 | if 'price_unit' in res['value']: | 258 | if 'price_unit' in res['value']: |
37 | 253 | 259 | ||
38 | === modified file 'purchase_extended/test/process/bid2order.yml' | |||
39 | --- purchase_extended/test/process/bid2order.yml 2013-11-01 11:44:19 +0000 | |||
40 | +++ purchase_extended/test/process/bid2order.yml 2014-02-06 13:54:03 +0000 | |||
41 | @@ -22,11 +22,16 @@ | |||
42 | 22 | date_planned: '2013-08-30' | 22 | date_planned: '2013-08-30' |
43 | 23 | price_unit: 52.53 | 23 | price_unit: 52.53 |
44 | 24 | - | 24 | - |
46 | 25 | Type must be 'bid' and the total untaxed amount of the RFQ must be computed. | 25 | I print the RFQ. |
47 | 26 | - | ||
48 | 27 | !python {model: purchase.order}: | | ||
49 | 28 | self.print_quotation(cr, uid, [ref("purchase_order_ext_bid2order1")]) | ||
50 | 29 | - | ||
51 | 30 | Type must be 'rfq' and the total untaxed amount of the RFQ must be computed. | ||
52 | 26 | - | 31 | - |
53 | 27 | !assert {model: purchase.order, id: purchase_order_ext_bid2order1, string: The amount of RFQ is not correctly computed}: | 32 | !assert {model: purchase.order, id: purchase_order_ext_bid2order1, string: The amount of RFQ is not correctly computed}: |
56 | 28 | - type == 'bid' | 33 | - type == 'rfq' |
57 | 29 | - state == 'draftbid' | 34 | - state == 'sent' |
58 | 30 | - round(sum([l.price_subtotal for l in order_line]), 2) == round(amount_untaxed, 2) | 35 | - round(sum([l.price_subtotal for l in order_line]), 2) == round(amount_untaxed, 2) |
59 | 31 | - | 36 | - |
60 | 32 | I run the 'Bid encoded' wizard. I fill the date. | 37 | I run the 'Bid encoded' wizard. I fill the date. |
61 | 33 | 38 | ||
62 | === modified file 'purchase_extended/workflow/purchase_order.xml' | |||
63 | --- purchase_extended/workflow/purchase_order.xml 2013-08-07 12:39:30 +0000 | |||
64 | +++ purchase_extended/workflow/purchase_order.xml 2014-02-06 13:54:03 +0000 | |||
65 | @@ -55,6 +55,20 @@ | |||
66 | 55 | <field name="signal">purchase_cancel</field> | 55 | <field name="signal">purchase_cancel</field> |
67 | 56 | </record> | 56 | </record> |
68 | 57 | 57 | ||
69 | 58 | <record id="act_po_requisition_selected" model="workflow.activity"> | ||
70 | 59 | <field name="wkf_id" ref="purchase.purchase_order"/> | ||
71 | 60 | <field name="name">Bid selected</field> | ||
72 | 61 | <field name="kind">function</field> | ||
73 | 62 | <field name="action">po_tender_requisition_selected()</field> | ||
74 | 63 | <field name="flow_stop">True</field> | ||
75 | 64 | </record> | ||
76 | 65 | <record id="trans_po_requisition_selected" model="workflow.transition"> | ||
77 | 66 | <field name="act_from" ref="purchase.act_bid"/> | ||
78 | 67 | <field name="act_to" ref="act_po_requisition_selected"/> | ||
79 | 68 | <field name="signal">select_requisition</field> | ||
80 | 69 | </record> | ||
81 | 70 | |||
82 | 71 | |||
83 | 58 | <!-- Rename some activities to make the workflow clear --> | 72 | <!-- Rename some activities to make the workflow clear --> |
84 | 59 | <record id="purchase.act_draft" model="workflow.activity"> | 73 | <record id="purchase.act_draft" model="workflow.activity"> |
85 | 60 | <field name="name">draft RFQ</field> | 74 | <field name="name">draft RFQ</field> |
86 | 61 | 75 | ||
87 | === modified file 'purchase_requisition_extended/model/purchase_order.py' | |||
88 | --- purchase_requisition_extended/model/purchase_order.py 2014-02-06 09:49:41 +0000 | |||
89 | +++ purchase_requisition_extended/model/purchase_order.py 2014-02-06 13:54:03 +0000 | |||
90 | @@ -62,6 +62,8 @@ | |||
91 | 62 | 'incoterm_address': requisition.req_incoterm_address, | 62 | 'incoterm_address': requisition.req_incoterm_address, |
92 | 63 | 'transport_mode_id': requisition.req_transport_mode_id, | 63 | 'transport_mode_id': requisition.req_transport_mode_id, |
93 | 64 | }) | 64 | }) |
94 | 65 | if requisition.pricelist_id: | ||
95 | 66 | values.update({'pricelist_id': requisition.pricelist_id.id}) | ||
96 | 65 | return values | 67 | return values |
97 | 66 | 68 | ||
98 | 67 | def copy(self, cr, uid, id, default=None, context=None): | 69 | def copy(self, cr, uid, id, default=None, context=None): |
99 | 68 | 70 | ||
100 | === modified file 'purchase_requisition_extended/model/purchase_requisition.py' | |||
101 | --- purchase_requisition_extended/model/purchase_requisition.py 2013-11-01 11:44:19 +0000 | |||
102 | +++ purchase_requisition_extended/model/purchase_requisition.py 2014-02-06 13:54:03 +0000 | |||
103 | @@ -25,15 +25,33 @@ | |||
104 | 25 | domain=[('type', 'in', ('rfq', 'bid'))]), | 25 | domain=[('type', 'in', ('rfq', 'bid'))]), |
105 | 26 | # new | 26 | # new |
106 | 27 | 'req_validity': fields.date("Requested Bid's End of Validity", | 27 | 'req_validity': fields.date("Requested Bid's End of Validity", |
109 | 28 | help="Default value requested to " | 28 | help="Requested validity period requested to the bidder, " |
110 | 29 | "the supplier."), | 29 | "i.e. please send bids that stay valid until that " |
111 | 30 | "date.\n The bidder is allowed to send a bid with " | ||
112 | 31 | "another validity end date that gets encoded in the " | ||
113 | 32 | "bid."), | ||
114 | 30 | 'bid_tendering_mode': fields.selection([('open', 'Open'), | 33 | 'bid_tendering_mode': fields.selection([('open', 'Open'), |
115 | 31 | ('restricted', 'Restricted')], | 34 | ('restricted', 'Restricted')], |
116 | 32 | 'Call for Bids Mode'), | 35 | 'Call for Bids Mode'), |
117 | 36 | help="- Restricted : you select yourself the " | ||
118 | 37 | "bidders and generate a RFQ for each of " | ||
119 | 38 | "those. \n" | ||
120 | 39 | "- Open : anybody can bid (you have to " | ||
121 | 40 | "advertise the call for bids) and you " | ||
122 | 41 | "directly encode the bids you received. " | ||
123 | 42 | "You are still able to generate RFQ if " | ||
124 | 43 | "you want to contact usual bidders."), | ||
125 | 33 | 'bid_receipt_mode': fields.selection([('open', 'Open'), | 44 | 'bid_receipt_mode': fields.selection([('open', 'Open'), |
126 | 34 | ('sealed', 'Sealed')], | 45 | ('sealed', 'Sealed')], |
127 | 35 | 'Bid Receipt Mode', | 46 | 'Bid Receipt Mode', |
128 | 36 | required=True), | 47 | required=True), |
129 | 48 | help="- Open : The bids can be opened when " | ||
130 | 49 | "received and encoded. \n" | ||
131 | 50 | "- Closed : The bids can be marked as " | ||
132 | 51 | "received but they have to be opened \n" | ||
133 | 52 | "all at the same time after an opening " | ||
134 | 53 | "ceremony (probably specific to public " | ||
135 | 54 | "sector)."), | ||
136 | 37 | 'consignee_id': fields.many2one('res.partner', | 55 | 'consignee_id': fields.many2one('res.partner', |
137 | 38 | 'Consignee', | 56 | 'Consignee', |
138 | 39 | help="Person responsible of delivery"), | 57 | help="Person responsible of delivery"), |
139 | @@ -55,6 +73,13 @@ | |||
140 | 55 | 'account.payment.term', | 73 | 'account.payment.term', |
141 | 56 | 'Requested Payment Term', | 74 | 'Requested Payment Term', |
142 | 57 | help="Default value requested to the supplier."), | 75 | help="Default value requested to the supplier."), |
143 | 76 | 'pricelist_id': fields.many2one('product.pricelist', | ||
144 | 77 | 'Pricelist', | ||
145 | 78 | help="If set that pricelist will be used to generate the RFQ." | ||
146 | 79 | "Mostely used to ask a requisition in a given currency."), | ||
147 | 80 | 'date_end': fields.datetime('Bid Submission Deadline', | ||
148 | 81 | help="All bids received after that date won't be valid " | ||
149 | 82 | " (probably specific to public sector)."), | ||
150 | 58 | } | 83 | } |
151 | 59 | _defaults = { | 84 | _defaults = { |
152 | 60 | 'bid_receipt_mode': 'open', | 85 | 'bid_receipt_mode': 'open', |
153 | @@ -83,6 +108,8 @@ | |||
154 | 83 | 'incoterm_id': requisition.req_incoterm_id.id, | 108 | 'incoterm_id': requisition.req_incoterm_id.id, |
155 | 84 | 'incoterm_address': requisition.req_incoterm_address, | 109 | 'incoterm_address': requisition.req_incoterm_address, |
156 | 85 | }) | 110 | }) |
157 | 111 | if requisition.pricelist_id: | ||
158 | 112 | values['pricelist_id'] = requisition.pricelist_id.id | ||
159 | 86 | return values | 113 | return values |
160 | 87 | 114 | ||
161 | 88 | def _prepare_purchase_order_line(self, cr, uid, requisition, | 115 | def _prepare_purchase_order_line(self, cr, uid, requisition, |
162 | @@ -142,18 +169,34 @@ | |||
163 | 142 | context=context) | 169 | context=context) |
164 | 143 | return super(PurchaseRequisition, self).generate_po(cr, uid, [ids], context=context) | 170 | return super(PurchaseRequisition, self).generate_po(cr, uid, [ids], context=context) |
165 | 144 | 171 | ||
166 | 172 | def quotation_selected(self, cr, uid, quotation, context=None): | ||
167 | 173 | """Predicate that checks if a quotation has at least one line chosen | ||
168 | 174 | :param quotation: record of 'purchase.order' | ||
169 | 175 | |||
170 | 176 | :returns: True if one line has been chosen | ||
171 | 177 | |||
172 | 178 | """ | ||
173 | 179 | # This topic is subject to changes | ||
174 | 180 | return quotation.bid_partial | ||
175 | 181 | |||
176 | 145 | def cancel_quotation(self, cr, uid, tender, context=None): | 182 | def cancel_quotation(self, cr, uid, tender, context=None): |
177 | 146 | """ | 183 | """ |
178 | 147 | Called from generate_po. Cancel only draft and sent rfq | 184 | Called from generate_po. Cancel only draft and sent rfq |
179 | 148 | """ | 185 | """ |
180 | 149 | po = self.pool.get('purchase.order') | 186 | po = self.pool.get('purchase.order') |
181 | 150 | wf_service = netsvc.LocalService("workflow") | 187 | wf_service = netsvc.LocalService("workflow") |
182 | 188 | tender.refresh() | ||
183 | 151 | for quotation in tender.purchase_ids: | 189 | for quotation in tender.purchase_ids: |
189 | 152 | if quotation.state in ['draft', 'sent']: | 190 | if quotation.state in ['draft', 'sent', 'bid']: |
190 | 153 | wf_service.trg_validate(uid, 'purchase.order', quotation.id, 'purchase_cancel', cr) | 191 | if self.quotation_selected(cr, uid, quotation, context=context): |
191 | 154 | po.message_post(cr, uid, [quotation.id], | 192 | wf_service.trg_validate(uid, 'purchase.order', quotation.id, |
192 | 155 | body=_('Canceled by the call for bids associated to this request for quotation.'), | 193 | 'select_requisition', cr) |
193 | 156 | context=context) | 194 | else: |
194 | 195 | wf_service.trg_validate(uid, 'purchase.order', quotation.id, 'purchase_cancel', cr) | ||
195 | 196 | po.message_post(cr, uid, [quotation.id], | ||
196 | 197 | body=_('Canceled by the call for bids associated' | ||
197 | 198 | ' to this request for quotation.'), | ||
198 | 199 | context=context) | ||
199 | 157 | 200 | ||
200 | 158 | return True | 201 | return True |
201 | 159 | 202 | ||
202 | @@ -226,19 +269,6 @@ | |||
203 | 226 | res['domain'] = expression.AND([eval(res.get('domain', [])), [('requisition_id', 'in', ids)]]) | 269 | res['domain'] = expression.AND([eval(res.get('domain', [])), [('requisition_id', 'in', ids)]]) |
204 | 227 | return res | 270 | return res |
205 | 228 | 271 | ||
206 | 229 | def open_product_line(self, cr, uid, ids, context=None): | ||
207 | 230 | """ Filter to show only lines from bids received. Group by requisition line instead of product for unicity | ||
208 | 231 | """ | ||
209 | 232 | res = super(PurchaseRequisition, self).open_product_line(cr, uid, ids, context=context) | ||
210 | 233 | ctx = res.setdefault('context', {}) | ||
211 | 234 | if 'search_default_groupby_product' in ctx: | ||
212 | 235 | del ctx['search_default_groupby_product'] | ||
213 | 236 | if 'search_default_hide_cancelled' in ctx: | ||
214 | 237 | del ctx['search_default_hide_cancelled'] | ||
215 | 238 | ctx['search_default_groupby_requisitionline'] = True | ||
216 | 239 | ctx['search_default_showbids'] = True | ||
217 | 240 | return res | ||
218 | 241 | |||
219 | 242 | def close_callforbids(self, cr, uid, ids, context=None): | 272 | def close_callforbids(self, cr, uid, ids, context=None): |
220 | 243 | """ | 273 | """ |
221 | 244 | Check all quantities have been sourced | 274 | Check all quantities have been sourced |
222 | @@ -289,6 +319,19 @@ | |||
223 | 289 | 'context': ctx, | 319 | 'context': ctx, |
224 | 290 | } | 320 | } |
225 | 291 | 321 | ||
226 | 322 | def open_product_line(self, cr, uid, ids, context=None): | ||
227 | 323 | """ Filter to show only lines from bids received. Group by requisition line instead of product for unicity | ||
228 | 324 | """ | ||
229 | 325 | res = super(PurchaseRequisition, self).open_product_line(cr, uid, ids, context=context) | ||
230 | 326 | ctx = res.setdefault('context', {}) | ||
231 | 327 | if 'search_default_groupby_product' in ctx: | ||
232 | 328 | del ctx['search_default_groupby_product'] | ||
233 | 329 | if 'search_default_hide_cancelled' in ctx: | ||
234 | 330 | del ctx['search_default_hide_cancelled'] | ||
235 | 331 | ctx['search_default_groupby_requisitionline'] = True | ||
236 | 332 | ctx['search_default_showbids'] = True | ||
237 | 333 | return res | ||
238 | 334 | |||
239 | 292 | def close_callforbids_ok(self, cr, uid, ids, context=None): | 335 | def close_callforbids_ok(self, cr, uid, ids, context=None): |
240 | 293 | wf_service = netsvc.LocalService("workflow") | 336 | wf_service = netsvc.LocalService("workflow") |
241 | 294 | for id in ids: | 337 | for id in ids: |
242 | 295 | 338 | ||
243 | === modified file 'purchase_requisition_extended/view/purchase_requisition.xml' | |||
244 | --- purchase_requisition_extended/view/purchase_requisition.xml 2013-08-08 09:31:25 +0000 | |||
245 | +++ purchase_requisition_extended/view/purchase_requisition.xml 2014-02-06 13:54:03 +0000 | |||
246 | @@ -21,6 +21,7 @@ | |||
247 | 21 | </xpath> | 21 | </xpath> |
248 | 22 | <xpath expr="//field[@name='user_id']" position="after"> | 22 | <xpath expr="//field[@name='user_id']" position="after"> |
249 | 23 | <field name="bid_tendering_mode" attrs="{'readonly': [('state','not in',('draft'))]}" required="1"/> | 23 | <field name="bid_tendering_mode" attrs="{'readonly': [('state','not in',('draft'))]}" required="1"/> |
250 | 24 | <field name="pricelist_id" attrs="{'readonly': [('state','not in',('draft'))]}"/> | ||
251 | 24 | </xpath> | 25 | </xpath> |
252 | 25 | <separator string="Requests for Quotation" position="attributes"> | 26 | <separator string="Requests for Quotation" position="attributes"> |
253 | 26 | <attribute name="string">Requests for Quotation / Bids</attribute> | 27 | <attribute name="string">Requests for Quotation / Bids</attribute> |
Add a bid selected state on PO
Fix confirmation of cost estimate. PO in state bid will be cancel too