Merge lp:~mallorymarcot/unifield-server/partial_dev into lp:unifield-server

Proposed by jftempo
Status: Rejected
Rejected by: jftempo
Proposed branch: lp:~mallorymarcot/unifield-server/partial_dev
Merge into: lp:unifield-server
Diff against target: 35330 lines (+17188/-14622)
152 files modified
bin/addons/__init__.py (+19/-5)
bin/addons/analytic_distribution/wizard/analytic_distribution_wizard.py (+5/-4)
bin/addons/analytic_distribution_supply/purchase.py (+0/-12)
bin/addons/analytic_distribution_supply/purchase_view.xml (+2/-2)
bin/addons/analytic_distribution_supply/sale_view.xml (+2/-2)
bin/addons/delete_button/view/sale_view.xml (+1/-1)
bin/addons/delivery_mechanism/__openerp__.py (+0/-1)
bin/addons/delivery_mechanism/delivery_mechanism.py (+50/-76)
bin/addons/delivery_mechanism/wizard/enter_reason.py (+3/-1)
bin/addons/documents_done/__openerp__.py (+1/-1)
bin/addons/documents_done/documents_done.py (+5/-24)
bin/addons/kit/wizard/kit_selection.py (+1/-12)
bin/addons/msf_cross_docking/cross_docking.py (+2/-14)
bin/addons/msf_cross_docking/cross_docking_view.xml (+0/-40)
bin/addons/msf_custom_settings/view/purchase_view.xml (+20/-19)
bin/addons/msf_custom_settings/view/sale_view.xml (+1/-1)
bin/addons/msf_doc_import/__openerp__.py (+0/-2)
bin/addons/msf_doc_import/view/purchase_order_import_line_view.xml (+54/-5)
bin/addons/msf_doc_import/wizard/wizard_import_po_line.py (+1/-0)
bin/addons/msf_doc_import/wizard/wizard_po_simulation_screen.py (+5/-1)
bin/addons/msf_doc_import/workflow/procurement_request_workflow.xml (+3/-1)
bin/addons/msf_doc_import/workflow/purchase_workflow.xml (+0/-11)
bin/addons/msf_doc_import/workflow/sale_workflow.xml (+0/-8)
bin/addons/msf_order_date/__openerp__.py (+1/-2)
bin/addons/msf_order_date/order_dates.py (+3/-7)
bin/addons/msf_order_date/order_dates_view.xml (+8/-8)
bin/addons/msf_outgoing/msf_outgoing.py (+13/-27)
bin/addons/msf_outgoing/msf_outgoing_view.xml (+1/-1)
bin/addons/msf_processes/process/procurement_process.xml (+4/-4)
bin/addons/msf_processes/process/purchase_process.xml (+4/-4)
bin/addons/msf_processes/process/sale_process.xml (+4/-4)
bin/addons/msf_profile/__openerp__.py (+1/-2)
bin/addons/msf_profile/i18n/fr_MF.po (+1/-1)
bin/addons/msf_profile/purchase_double_validation_workflow.xml (+0/-18)
bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_10-lines-removed_all-groups.xml (+3/-3)
bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_10-lines-removed_no-cashier_no-productManager.xml (+3/-3)
bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_all-lines_all-groups.xml (+3/-3)
bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_all-lines_all-groups_misformed_no-header.xml (+3/-3)
bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_all-lines_no-cashier_no-productManager.xml (+3/-3)
bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_all-lines_no-groups.xml (+3/-3)
bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_original.csv (+3/-3)
bin/addons/msf_sync_data_server/data/sync_server.message_rule.csv (+5/-8)
bin/addons/order_types/__openerp__.py (+0/-1)
bin/addons/procurement_request/__openerp__.py (+7/-9)
bin/addons/procurement_request/procurement_request.py (+8/-116)
bin/addons/procurement_request/procurement_request_view.xml (+18/-16)
bin/addons/procurement_request/procurement_request_workflow.xml (+0/-86)
bin/addons/purchase/__init__.py (+3/-1)
bin/addons/purchase/__openerp__.py (+3/-2)
bin/addons/purchase/process/purchase_process.xml (+2/-1)
bin/addons/purchase/procurement_order.py (+152/-0)
bin/addons/purchase/purchase.py (+1612/-618)
bin/addons/purchase/purchase_data.xml (+43/-2)
bin/addons/purchase/purchase_line.py (+1422/-0)
bin/addons/purchase/purchase_line_workflow.xml (+234/-0)
bin/addons/purchase/purchase_view.xml (+202/-76)
bin/addons/purchase/purchase_workflow.py (+566/-0)
bin/addons/purchase/purchase_workflow.xml (+0/-209)
bin/addons/purchase/wizard/__init__.py (+1/-0)
bin/addons/purchase/wizard/purchase_line_cancel.py (+58/-0)
bin/addons/purchase/wizard/purchase_line_cancel_view.xml (+24/-0)
bin/addons/purchase_allocation_report/purchase_allocation_report.py (+2/-2)
bin/addons/purchase_double_validation/__openerp__.py (+2/-3)
bin/addons/purchase_double_validation/purchase_double_validation_workflow.xml (+0/-18)
bin/addons/purchase_followup/sale_purchase.py (+10/-4)
bin/addons/purchase_override/__init__.py (+28/-26)
bin/addons/purchase_override/__openerp__.py (+0/-1)
bin/addons/purchase_override/purchase.py (+16/-4360)
bin/addons/purchase_override/purchase_view.xml (+0/-331)
bin/addons/purchase_override/purchase_workflow.xml (+0/-163)
bin/addons/purchase_override/wizard/split_order_line.py (+32/-170)
bin/addons/register_accounting/__openerp__.py (+0/-1)
bin/addons/register_accounting/purchase_view.xml (+0/-36)
bin/addons/res_currency_functional/order_line_view.xml (+2/-2)
bin/addons/return_claim/return_claim.py (+0/-1)
bin/addons/sale/__init__.py (+25/-1)
bin/addons/sale/__openerp__.py (+8/-2)
bin/addons/sale/report/sale_report.py (+96/-22)
bin/addons/sale/report/sale_report_view.xml (+63/-30)
bin/addons/sale/res_partner.py (+95/-0)
bin/addons/sale/sale_data.xml (+46/-4)
bin/addons/sale/sale_order.py (+2737/-548)
bin/addons/sale/sale_order_line_workflow.xml (+238/-0)
bin/addons/sale/sale_sequence.xml (+4/-5)
bin/addons/sale/sale_view.xml (+401/-44)
bin/addons/sale/sale_workflow.py (+438/-0)
bin/addons/sale/sale_workflow.xml (+0/-243)
bin/addons/sale/test/data.yml (+170/-0)
bin/addons/sale/test/deactivate_product_valid_fo.yml (+41/-0)
bin/addons/sale/test/deactivate_product_validate_fo.yml (+49/-0)
bin/addons/sale/test/sale_test.yml (+370/-0)
bin/addons/sale/test/split_line.yml (+139/-0)
bin/addons/sale/wizard/__init__.py (+3/-1)
bin/addons/sale/wizard/delete_sale_order_line.py (+8/-6)
bin/addons/sale/wizard/order_change_currency.py (+100/-0)
bin/addons/sale/wizard/order_change_currency_view.xml (+27/-0)
bin/addons/sale/wizard/split_order_line.py (+118/-0)
bin/addons/sale/wizard/split_order_line_view.xml (+25/-0)
bin/addons/sale_override/__init__.py (+0/-31)
bin/addons/sale_override/__openerp__.py (+0/-55)
bin/addons/sale_override/report/__init__.py (+0/-1)
bin/addons/sale_override/report/sale_report.py (+0/-203)
bin/addons/sale_override/report/sale_report_view.xml (+0/-144)
bin/addons/sale_override/res_partner.py (+0/-95)
bin/addons/sale_override/sale.py (+0/-3777)
bin/addons/sale_override/sale_sequence.xml (+0/-19)
bin/addons/sale_override/sale_view.xml (+0/-587)
bin/addons/sale_override/sale_workflow.xml (+0/-143)
bin/addons/sale_override/test/data.yml (+0/-170)
bin/addons/sale_override/test/deactivate_product_valid_fo.yml (+0/-41)
bin/addons/sale_override/test/deactivate_product_validate_fo.yml (+0/-49)
bin/addons/sale_override/test/sale_test.yml (+0/-370)
bin/addons/sale_override/test/split_line.yml (+0/-139)
bin/addons/sale_override/wizard/__init__.py (+0/-3)
bin/addons/sale_override/wizard/order_change_currency.py (+0/-100)
bin/addons/sale_override/wizard/order_change_currency_view.xml (+0/-27)
bin/addons/sale_override/wizard/split_order_line.py (+0/-103)
bin/addons/sale_override/wizard/split_order_line_view.xml (+0/-25)
bin/addons/sales_followup/report/sale_follow_up_report_multi.py (+5/-3)
bin/addons/sales_followup/sale_followup.py (+21/-28)
bin/addons/sourcing/__openerp__.py (+0/-1)
bin/addons/sourcing/sale_order_line.py (+342/-15)
bin/addons/sourcing/sourcing_view.xml (+6/-8)
bin/addons/sourcing/wizard/multiple_sourcing.py (+6/-13)
bin/addons/specific_locations/specific_locations.py (+0/-21)
bin/addons/stock/stock.py (+12/-12)
bin/addons/stock_override/stock.py (+32/-195)
bin/addons/stock_override/stock_view.xml (+4/-3)
bin/addons/sync_client/message.py (+32/-36)
bin/addons/sync_client/sync_client.py (+1/-1)
bin/addons/sync_server/__openerp__.py (+1/-1)
bin/addons/sync_server/message.py (+0/-17)
bin/addons/sync_server/sync_server_menu.xml (+1/-1)
bin/addons/sync_so/purchase.py (+116/-295)
bin/addons/sync_so/purchase_view.xml (+4/-4)
bin/addons/sync_so/sale.py (+44/-0)
bin/addons/sync_so/so_po_common.py (+127/-6)
bin/addons/tender_flow/tender_flow.py (+164/-172)
bin/addons/tender_flow/tender_flow_view.xml (+115/-56)
bin/addons/tender_flow/tender_flow_workflow.xml (+2/-53)
bin/addons/transport_mgmt/purchase_view.xml (+0/-26)
bin/addons/unifield_tests/testfield/files/TC2 SSL VI import.xml (+329/-0)
bin/addons/unifield_tests/testfield/init_data/set_rb_partial_tf.py (+114/-0)
bin/addons/unifield_tests/testfield/meta_features/tc1.meta_feature (+1563/-0)
bin/addons/unifield_tests/testfield/meta_features/tc1_intermission.meta_feature (+1577/-0)
bin/addons/unifield_tests/testfield/meta_features/tc2.meta_feature (+924/-0)
bin/addons/unifield_tests/testfield/meta_features/tc3.meta_feature (+1103/-0)
bin/addons/unifield_tests/testfield/meta_features/tc4.meta_feature (+604/-0)
bin/addons/useability_dashboard_and_menu/menu/warehouse_menu.xml (+1/-1)
bin/addons/useability_dashboard_and_menu/view/purchase_view.xml (+2/-2)
bin/addons/useability_dashboard_and_menu/view/sale_view.xml (+0/-25)
bin/osv/orm.py (+47/-34)
To merge this branch: bzr merge lp:~mallorymarcot/unifield-server/partial_dev
Reviewer Review Type Date Requested Status
UniField Reviewer Team Pending
Review via email: mp+331325@code.launchpad.net
To post a comment you must log in.
4658. By Mallory MARCOT

Adding tc1_intermission and tc4 fro testfield

4659. By Mallory MARCOT

tc1_intermission OK

4660. By Mallory MARCOT

Fix issue taking resourced state instead of real state

4661. By Mallory MARCOT

Can now cancel a PO from header

4662. By Mallory MARCOT

If PO line is in draft state, simply remove it instead of cancelling it

Unmerged revisions

4662. By Mallory MARCOT

If PO line is in draft state, simply remove it instead of cancelling it

4661. By Mallory MARCOT

Can now cancel a PO from header

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/addons/__init__.py'
2--- bin/addons/__init__.py 2016-08-31 07:58:29 +0000
3+++ bin/addons/__init__.py 2017-10-02 12:34:11 +0000
4@@ -409,9 +409,16 @@
5 def init_module_objects(cr, module_name, obj_list):
6 logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: creating or updating database tables' % module_name)
7 todo = []
8+ missing_fk = {}
9 for obj in obj_list:
10 try:
11- result = obj._auto_init(cr, {'module': module_name})
12+ auto_init = obj._auto_init(cr, {'module': module_name})
13+ result = None
14+ if auto_init:
15+ result, fk = auto_init
16+ for obj, missing in fk.iteritems():
17+ missing_fk.setdefault(obj, [])
18+ missing_fk[obj] += missing
19 except Exception, e:
20 raise
21 if result:
22@@ -423,7 +430,7 @@
23 for t in todo:
24 t[1](cr, *t[2])
25 cr.commit()
26-
27+ return missing_fk
28
29 def register_class(m):
30 """
31@@ -512,7 +519,7 @@
32 assert stage in ('pre', 'post')
33 stageformat = {'pre': '[>%s]',
34 'post': '[%s>]',
35- }
36+ }
37
38 if not (hasattr(pkg, 'update') or pkg.state == 'to upgrade'):
39 return
40@@ -541,7 +548,7 @@
41
42 mapping = {'module': opj(pkg.name, 'migrations'),
43 'maintenance': opj('base', 'maintenance', 'migrations', pkg.name),
44- }
45+ }
46
47 for x in mapping.keys():
48 if version in m[x]:
49@@ -722,6 +729,7 @@
50 modobj = None
51 logger.notifyChannel('init', netsvc.LOG_DEBUG, 'loading %d packages..' % len(graph))
52
53+ missing_fk = {}
54 for package in graph:
55 if skip_modules and package.name in skip_modules:
56 continue
57@@ -730,9 +738,15 @@
58 register_class(package.name)
59 modules = pool.instanciate(package.name, cr)
60 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
61- init_module_objects(cr, package.name, modules)
62+ fk = init_module_objects(cr, package.name, modules)
63+ for obj, missing in fk.iteritems():
64+ missing_fk.setdefault(obj, [])
65+ missing_fk[obj] += missing
66 cr.commit()
67
68+ for related_obj, to_create in missing_fk.iteritems():
69+ for x in to_create:
70+ x[0]._create_fk(cr, x[1], x[2], x[3])
71 for package in graph:
72 status['progress'] = (float(statusi)+0.1) / len(graph)
73 m = package.name
74
75=== modified file 'bin/addons/analytic_distribution/wizard/analytic_distribution_wizard.py'
76--- bin/addons/analytic_distribution/wizard/analytic_distribution_wizard.py 2017-08-18 13:33:40 +0000
77+++ bin/addons/analytic_distribution/wizard/analytic_distribution_wizard.py 2017-10-02 12:34:11 +0000
78@@ -492,10 +492,10 @@
79 for el in self.browse(cr, uid, ids, context=context):
80 res[el.id] = True
81 # verify purchase state
82- if el.purchase_id and el.purchase_id.state not in ['draft', 'confirmed']:
83+ if el.purchase_id and el.purchase_id.state not in ['draft', 'draft_p', 'validated_n', 'validated']:
84 res[el.id] = False
85 # verify purchase line state
86- if el.purchase_line_id and el.purchase_line_id.order_id and el.purchase_line_id.order_id.state not in ['draft', 'confirmed']:
87+ if el.purchase_line_id and el.purchase_line_id.order_id and el.purchase_line_id.order_id.state not in ['draft', 'validated_n', 'validated']:
88 res[el.id] = False
89 # verify invoice state
90 if el.invoice_id and el.invoice_id.state in ['open', 'paid']:
91@@ -513,10 +513,10 @@
92 if el.accrual_line_id and el.accrual_line_id.state != 'draft':
93 res[el.id] = False
94 # verify sale order state
95- if el.sale_order_id and el.sale_order_id.state not in ['draft', 'validated']:
96+ if el.sale_order_id and el.sale_order_id.state not in ['draft', 'draft_p', 'validated']:
97 res[el.id] = False
98 # verify sale order line state
99- if el.sale_order_line_id and el.sale_order_line_id.order_id and el.sale_order_line_id.order_id.state not in ['draft', 'validated']:
100+ if el.sale_order_line_id and el.sale_order_line_id.order_id and el.sale_order_line_id.order_id.state not in ['draft', 'draft_p', 'validated']:
101 res[el.id] = False
102 # verify move state
103 # UFTP-363: Do not edit any element of JI or JE if the JE is imported
104@@ -1062,6 +1062,7 @@
105 if getattr(wiz, el[0], False):
106 obj_id = getattr(wiz, el[0], False).id
107 self.pool.get(el[1]).write(cr, uid, [obj_id], {'analytic_distribution_id': distrib_id}, context=context)
108+
109 # Finally do registration for each type
110 new_distrib = False
111 for line_type in ['cost.center', 'funding.pool', 'free.1', 'free.2']:
112
113=== modified file 'bin/addons/analytic_distribution_supply/purchase.py'
114--- bin/addons/analytic_distribution_supply/purchase.py 2017-02-07 13:18:54 +0000
115+++ bin/addons/analytic_distribution_supply/purchase.py 2017-10-02 12:34:11 +0000
116@@ -255,18 +255,6 @@
117 self.pool.get('account.commitment').action_commitment_done(cr, uid, [x.id for x in po.commitment_ids], context=context)
118 return True
119
120- def wkf_action_cancel_po(self, cr, uid, ids, context=None):
121- """
122- Delete commitment from purchase before 'cancel' state.
123- """
124- # Some verifications
125- if not context:
126- context = {}
127- if isinstance(ids, (int, long)):
128- ids = [ids]
129- # Change commitments state if exists
130- self._finish_commitment(cr, uid, ids, context=context)
131- return super(purchase_order, self).wkf_action_cancel_po(cr, uid, ids, context=context)
132
133 def action_done(self, cr, uid, ids, context=None):
134 """
135
136=== modified file 'bin/addons/analytic_distribution_supply/purchase_view.xml'
137--- bin/addons/analytic_distribution_supply/purchase_view.xml 2016-11-04 12:57:37 +0000
138+++ bin/addons/analytic_distribution_supply/purchase_view.xml 2017-10-02 12:34:11 +0000
139@@ -15,11 +15,11 @@
140 <newline />
141 <group colspan="4" col="8" attrs="{'invisible': ['|', ('analytic_distribution_id', '=', False), ('rfq_ok' ,'=', True)]}">
142 <button name="button_analytic_distribution" string="Analytical Distribution" type="object" icon="terp-check" context="context" colspan="4" attrs="{'invisible': [('analytic_distribution_id', '=', False)]}"/>
143- <button name="button_reset_distribution" string="Reset AD at line level" type="object" icon="gtk-undelete" colspan="4" states="draft,confirmed"/>
144+ <button name="button_reset_distribution" string="Reset AD at line level" type="object" icon="gtk-undelete" colspan="4" states="draft,validated"/>
145 </group>
146 <group colspan="4" col="8" attrs="{'invisible': ['|', ('analytic_distribution_id', '!=', False), ('rfq_ok', '=', True)]}">
147 <button name="button_analytic_distribution" string="Analytical Distribution" type="object" icon="terp-emblem-important" context="context" colspan="4" attrs="{'invisible': [('analytic_distribution_id', '!=', False)]}"/>
148- <button name="button_reset_distribution" string="Reset AD at line level" type="object" icon="gtk-undelete" colspan="4" states="draft,confirmed"/>
149+ <button name="button_reset_distribution" string="Reset AD at line level" type="object" icon="gtk-undelete" colspan="4" states="draft,validated"/>
150 </group>
151 <field name="analytic_distribution_id" invisible="1"/>
152 </xpath>
153
154=== modified file 'bin/addons/analytic_distribution_supply/sale_view.xml'
155--- bin/addons/analytic_distribution_supply/sale_view.xml 2016-11-04 12:57:37 +0000
156+++ bin/addons/analytic_distribution_supply/sale_view.xml 2017-10-02 12:34:11 +0000
157@@ -14,11 +14,11 @@
158 <xpath expr="/form/notebook" position="before">
159 <group colspan="4" col="8" attrs="{'invisible': [('analytic_distribution_id', '=', False)]}">
160 <button name="button_analytic_distribution" string="Analytical Distribution" type="object" icon="terp-check" context="context" colspan="4" attrs="{'invisible': [('analytic_distribution_id', '=', False)]}"/>
161- <button name="button_reset_distribution" string="Reset AD at line level" type="object" icon="gtk-undelete" colspan="4" states="draft,validated"/>
162+ <button name="button_reset_distribution" string="Reset AD at line level" type="object" icon="gtk-undelete" colspan="4" states="draft,draft_p,validated,validated_p"/>
163 </group>
164 <group colspan="4" col="8" attrs="{'invisible': [('analytic_distribution_id', '!=', False)]}">
165 <button name="button_analytic_distribution" string="Analytical Distribution" type="object" icon="terp-emblem-important" context="context" colspan="4" attrs="{'invisible': [('analytic_distribution_id', '!=', False)]}"/>
166- <button name="button_reset_distribution" string="Reset AD at line level" type="object" icon="gtk-undelete" colspan="4" states="draft,validated"/>
167+ <button name="button_reset_distribution" string="Reset AD at line level" type="object" icon="gtk-undelete" colspan="4" states="draft,draft_p,validated,validated_p"/>
168 </group>
169 <field name="analytic_distribution_id" invisible="1"/>
170 <group colspan="3"/>
171
172=== modified file 'bin/addons/delete_button/view/sale_view.xml'
173--- bin/addons/delete_button/view/sale_view.xml 2016-11-04 12:57:37 +0000
174+++ bin/addons/delete_button/view/sale_view.xml 2017-10-02 12:34:11 +0000
175@@ -29,7 +29,7 @@
176 <xpath expr="/tree" position="attributes">
177 <attribute name='hide_delete_button'>1</attribute>
178 </xpath>
179- <xpath expr="/tree/field[@name='state_hidden_sale_order']" position="after">
180+ <xpath expr="/tree/field[last()]" position="after">
181 <button name="delete_button" type="object" icon="gtk-del" string="Delete"
182 attrs="{'invisible' : [('state','!=','draft')]}" confirm='Do you really want to delete selected record(s) ?'/>
183 </xpath>
184
185=== modified file 'bin/addons/delivery_mechanism/__openerp__.py'
186--- bin/addons/delivery_mechanism/__openerp__.py 2013-03-25 16:48:26 +0000
187+++ bin/addons/delivery_mechanism/__openerp__.py 2017-10-02 12:34:11 +0000
188@@ -28,7 +28,6 @@
189 "account",
190 "stock_override",
191 "purchase_override",
192- "sale_override",
193 "product_asset", # because of asset_id
194 "msf_outgoing",
195 ],
196
197=== modified file 'bin/addons/delivery_mechanism/delivery_mechanism.py'
198--- bin/addons/delivery_mechanism/delivery_mechanism.py 2017-09-03 15:15:05 +0000
199+++ bin/addons/delivery_mechanism/delivery_mechanism.py 2017-10-02 12:34:11 +0000
200@@ -257,11 +257,10 @@
201 '''
202 location_dest_id = super(stock_move, self)._get_location_for_internal_request(cr, uid, context=context, **kwargs)
203 move = kwargs['move']
204- if move.purchase_line_id:
205- proc = move.purchase_line_id.procurement_id
206- if proc and proc.sale_order_line_ids and proc.sale_order_line_ids[0].order_id and proc.sale_order_line_ids[0].order_id.procurement_request:
207- if proc.sale_order_line_ids[0].order_id.location_requestor_id.usage != 'customer':
208- location_dest_id = proc.sale_order_line_ids[0].order_id.location_requestor_id.id
209+ linked_sol = move.purchase_line_id.linked_sol_id or False
210+ if linked_sol and linked_sol.procurement_request and linked_sol.order_id.location_requestor_id.usage != 'customer':
211+ location_dest_id = linked_sol.order_id.location_requestor_id.id
212+
213 return location_dest_id
214
215 def _do_partial_hook(self, cr, uid, ids, context, *args, **kwargs):
216@@ -303,56 +302,46 @@
217 ids = [ids]
218
219 # objects
220- so_line_obj = self.pool.get('sale.order.line')
221-
222 res = {}
223 for obj in self.browse(cr, uid, ids, context=context,
224 fields_to_fetch=['picking_id', 'purchase_line_id', 'id']):
225 res[obj.id] = {'move_id': False, 'picking_id': False, 'picking_version': 0, 'quantity': 0, 'moves': []}
226 if obj.picking_id and obj.picking_id.type == 'in':
227 # we are looking for corresponding OUT move from sale order line
228- if obj.purchase_line_id:
229- # linekd to a po
230- if obj.purchase_line_id.procurement_id:
231- # on order
232- procurement_id = obj.purchase_line_id.procurement_id.id
233- # find the corresponding sale order line
234- so_line_ids = so_line_obj.search(cr, uid, [('procurement_id', '=', procurement_id)], context=context)
235- # if the procurement comes from replenishment rules, there will be a procurement, but no associated sale order line
236- # we therefore do not raise an exception, but handle the case only if sale order lines are found
237- if so_line_ids:
238- # find the corresponding OUT move
239- move_ids = self.search(cr, uid, [('product_id', '=', data_back['product_id']),
240- ('state', 'in', ('assigned', 'confirmed')),
241- ('sale_line_id', '=', so_line_ids[0]),
242- ('in_out_updated', '=', False),
243- ('picking_id.type', '=', 'out'),
244- ('processed_stock_move', '=', False),
245- ], order="state desc", context=context)
246- # list of matching out moves
247- integrity_check = []
248- for move in self.browse(cr, uid, move_ids, context=context):
249- pick = move.picking_id
250- cond1 = move.picking_id.subtype == 'standard'
251- cond2 = move.product_qty != 0.00 and pick.subtype == 'picking' and (not pick.backorder_id or pick.backorder_id.subtype == 'standard') and pick.state == 'draft'
252- if cond2 or cond1:
253- integrity_check.append(move)
254- # return the first one matching
255- if integrity_check:
256- if all([not move.processed_stock_move for move in integrity_check]):
257- # the out stock moves (draft picking or std out) have not yet been processed, we can therefore update them
258- res[obj.id].update({
259- 'move_id': integrity_check[0].id,
260- 'moves': integrity_check,
261- 'picking_id': integrity_check[0].picking_id.id,
262- 'picking_version': integrity_check[0].picking_id.update_version_from_in_stock_picking,
263- 'quantity': integrity_check[0].product_qty,
264- })
265- else:
266- # the corresponding OUT move have been processed completely or partially,, we do not update the OUT
267- msg_log = _('The Stock Move %s from %s has already been processed and is '
268- 'therefore not updated.') % (integrity_check[0].name, integrity_check[0].picking_id.name)
269- self.log(cr, uid, integrity_check[0].id, msg_log)
270+ if obj.purchase_line_id and obj.purchase_line_id.linked_sol_id:
271+ # find the corresponding OUT move
272+ move_ids = self.search(cr, uid, [('product_id', '=', data_back['product_id']),
273+ ('state', 'in', ('assigned', 'confirmed')),
274+ ('sale_line_id', '=', obj.purchase_line_id.linked_sol_id.id),
275+ ('in_out_updated', '=', False),
276+ ('picking_id.type', '=', 'out'),
277+ ('processed_stock_move', '=', False),
278+ ], order="state desc", context=context)
279+ # list of matching out moves
280+ integrity_check = []
281+ for move in self.browse(cr, uid, move_ids, context=context):
282+ pick = move.picking_id
283+ cond1 = move.picking_id.subtype == 'standard'
284+ cond2 = move.product_qty != 0.00 and pick.subtype == 'picking' and (not pick.backorder_id or pick.backorder_id.subtype == 'standard') and pick.state == 'draft'
285+ # move from draft picking or standard picking
286+ if cond2 or cond1:
287+ integrity_check.append(move)
288+ # return the first one matching
289+ if integrity_check:
290+ if all([not move.processed_stock_move for move in integrity_check]):
291+ # the out stock moves (draft picking or std out) have not yet been processed, we can therefore update them
292+ res[obj.id].update({
293+ 'move_id': integrity_check[0].id,
294+ 'moves': integrity_check,
295+ 'picking_id': integrity_check[0].picking_id.id,
296+ 'picking_version': integrity_check[0].picking_id.update_version_from_in_stock_picking,
297+ 'quantity': integrity_check[0].product_qty,
298+ })
299+ else:
300+ # the corresponding OUT move have been processed completely or partially,, we do not update the OUT
301+ msg_log = _('The Stock Move %s from %s has already been processed and is '
302+ 'therefore not updated.') % (integrity_check[0].name, integrity_check[0].picking_id.name)
303+ self.log(cr, uid, integrity_check[0].id, msg_log)
304
305 else:
306 # we are looking for corresponding IN from on_order purchase order
307@@ -632,9 +621,6 @@
308 'product_uos': data_back['product_uom'],
309 'product_uos_qty': diff_qty, }, context=context)
310 move_obj.action_confirm(cr, uid, [new_move_id], context=context)
311-# if present_qty == 0.00:
312-# move_obj.write(cr, uid, [out_move_id], {'state': 'draft'})
313-# move_obj.unlink(cr, uid, out_move_id, context=context)
314 else:
315 move_obj.write(cr, uid, [out_move_id], {'product_qty': new_qty,
316 'product_uom': data['product_uom'][0],
317@@ -931,7 +917,6 @@
318 # Objects
319 inc_proc_obj = self.pool.get('stock.incoming.processor')
320 move_proc_obj = self.pool.get('stock.move.in.processor')
321- proc_obj = self.pool.get('procurement.order')
322 loc_obj = self.pool.get('stock.location')
323 uom_obj = self.pool.get('product.uom')
324 move_obj = self.pool.get('stock.move')
325@@ -961,7 +946,6 @@
326 backorder_id = False
327
328 internal_loc = loc_obj.search(cr, uid, [('usage', '=', 'internal'), ('cross_docking_location_ok', '=', False)])
329- proc_loc_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'location_procurement')[1]
330 context['location'] = internal_loc
331
332 product_availability = {}
333@@ -1182,6 +1166,10 @@
334 # and the remaining quantity to list of moves to put in backorder
335 if diff_qty > 0.00 and move.state != 'cancel':
336 backordered_moves.append((move, diff_qty, average_values, data_back, move_sptc_values))
337+ # decrement qty of linked INTernal move:
338+ internal_move = self.pool.get('stock.move').search(cr, uid, [('linked_incoming_move', '=', move.id)], context=context)
339+ if internal_move:
340+ move_obj.write(cr, uid, internal_move, {'product_qty': diff_qty, 'product_uos_qty': diff_qty}, context=context)
341 else:
342 for sptc_values in move_sptc_values:
343 # track change that will be created:
344@@ -1190,20 +1178,10 @@
345 'transaction_name': _('Reception %s') % move.picking_id.name,
346 'sptc_values': sptc_values.copy(),
347 })
348-
349- # UTP-967
350- if move.state != 'cancel' and move.purchase_line_id and move.purchase_line_id.procurement_id:
351- proc = move.purchase_line_id.procurement_id
352- if proc.move_id and proc.move_id.location_id.id == proc_loc_id:
353- if diff_qty > 0:
354- # REF-59: move of partial in,
355- # adapt proc order's move qty (for correct virtual stock)
356- move_obj.write(cr, uid, [proc.move_id.id],
357- {'product_qty': diff_qty}, context=context)
358- else:
359- proc_obj.write(cr, uid, [proc.id], {'move_id': move.id}, context=context)
360- # note: do not close move until a diff qty is applied above
361- move_obj.write(cr, uid, [proc.move_id.id], {'product_qty': 0.00, 'state': 'done'}, context=context)
362+ # cancel linked INTernal move (INT):
363+ internal_move = self.pool.get('stock.move').search(cr, uid, [('linked_incoming_move', '=', move.id)], context=context)
364+ if internal_move:
365+ move_obj.action_cancel(cr, uid, internal_move, context=context)
366
367 prog_id = self.update_processing_info(cr, uid, picking_id, prog_id, {
368 'progress_line': _('Done (%s/%s)') % (move_done, total_moves),
369@@ -1286,8 +1264,11 @@
370 bo_values.update(av_values)
371 context['keepLineNumber'] = True
372 context['from_button'] = False
373- move_obj.copy(cr, uid, bo_move.id, bo_values, context=context)
374+ new_bo_move_id = move_obj.copy(cr, uid, bo_move.id, bo_values, context=context)
375 context['keepLineNumber'] = False
376+ # update linked INT move with new BO move id:
377+ internal_move = move_obj.search(cr, uid, [('linked_incoming_move', '=', bo_move.id)], context=context)
378+ move_obj.write(cr, uid, internal_move, {'linked_incoming_move': new_bo_move_id}, context=context)
379
380 # Put the done moves in this new picking
381 done_values = {'picking_id': backorder_id}
382@@ -1491,13 +1472,6 @@
383 # If there are still some lines available with qty 0, then check if any in progress PICK, if all complete, then close the PICK
384 self.validate(cr, uid, [mirror_pick.id], context=context)
385
386- if move.purchase_line_id and move.purchase_line_id.procurement_id:
387- procurement = move.purchase_line_id.procurement_id
388- if not procurement.sale_id and procurement.move_id:
389- self.pool.get('stock.move').action_cancel(cr, uid, [move.purchase_line_id.procurement_id.move_id.id])
390- wf_service.trg_validate(uid, 'procurement.order', move.purchase_line_id.procurement_id.id, 'button_cancel', cr)
391-
392-
393 # correct the corresponding po manually if exists - should be in shipping exception
394 if obj.purchase_id:
395 wf_service.trg_validate(uid, 'purchase.order', obj.purchase_id.id, 'picking_ok', cr)
396
397=== modified file 'bin/addons/delivery_mechanism/wizard/enter_reason.py'
398--- bin/addons/delivery_mechanism/wizard/enter_reason.py 2015-12-04 16:00:07 +0000
399+++ bin/addons/delivery_mechanism/wizard/enter_reason.py 2017-10-02 12:34:11 +0000
400@@ -80,7 +80,9 @@
401 context['pol_qty'] = pol_qty
402 context['from_in_cancel'] = True
403 pol_obj.write(cr, uid, pol_ids, {'has_to_be_resourced': True}, context=context)
404- pol_obj.cancel_sol(cr, uid, pol_ids, context=context)
405+ for pol in self.pool.get('purchase.order.line').browse(cr, uid, pol_ids, context=context):
406+ if pol.linked_sol_id:
407+ wf_service.trg_validate(uid, 'sale.order.line', pol.linked_sol_id.id, 'cancel', cr)
408
409 # cancel the IN
410 wf_service.trg_validate(uid, 'stock.picking', obj.id, 'button_cancel', cr)
411
412=== modified file 'bin/addons/documents_done/__openerp__.py'
413--- bin/addons/documents_done/__openerp__.py 2011-12-30 09:19:57 +0000
414+++ bin/addons/documents_done/__openerp__.py 2017-10-02 12:34:11 +0000
415@@ -23,7 +23,7 @@
416 "name": "Documents to Done",
417 "version": "1.0",
418 "depends": [
419- "sale_override",
420+ "sale",
421 "purchase_override",
422 "stock_override",
423 "tender_flow",
424
425=== modified file 'bin/addons/documents_done/documents_done.py'
426--- bin/addons/documents_done/documents_done.py 2017-04-19 10:05:42 +0000
427+++ bin/addons/documents_done/documents_done.py 2017-10-02 12:34:11 +0000
428@@ -130,30 +130,16 @@
429 tender_ids = []
430 invoice_ids = []
431 for line in order.order_line:
432- # Check procurement orders
433- if line.procurement_id:
434- if line.procurement_id.state not in ('cancel', 'done'):
435- proc_ids.append(line.procurement_id.id)
436- # Check PO
437- if line.procurement_id.purchase_id and line.procurement_id.purchase_id.state not in ('cancel', 'done'):
438- po_ids.append(line.procurement_id.purchase_id.id)
439- # Check tenders
440- if line.procurement_id.tender_id and line.procurement_id.tender_id.state not in ('cancel', 'done'):
441- tender_ids.append(line.procurement_id.tender_id.id)
442- # Check Rfheck RfQ
443- for rfq in line.procurement_id.tender_id.rfq_ids:
444- if rfq.state not in ('cancel', 'done'):
445- po_ids.append(rfq.id)
446+ pol_id = self.pool.get('purchase.order.line').search(cr, uid, [('linked_sol_id', '=', line.id)], context=context)
447+ po_line = self.pool.get('purchase.order.line').browse(cr, uid, pol_id[0], context=context) if pol_id else False
448+ # Check PO
449+ if po_line and not po_line.state.startswith(('cancel', 'done')):
450+ po_ids.append(po_line.order_id.id)
451
452 # Check loan counterpart
453 if order.loan_id and order.loan_id.state not in ('cancel', 'done'):
454 po_ids.append(order.loan_id.id)
455
456- # Invoices
457- #for invoice in order.invoice_ids:
458- # if invoice.state not in ('cancel', 'paid'):
459- # invoice_ids.append(invoice.id)
460-
461 if context.get('count', False):
462 return move_ids or proc_ids or po_ids or tender_ids or invoice_ids or False
463 else:
464@@ -172,11 +158,6 @@
465 if order.loan_id and order.loan_id.state not in ('cancel', 'done'):
466 so_ids.append(order.loan_id.id)
467
468- # Invoices
469- #for invoice in order.invoice_ids:
470- # if invoice.state not in ('cancel', 'paid'):
471- # invoice_ids.append(invoice.id)
472-
473 if context.get('count', False):
474 return move_ids or so_ids or invoice_ids or False
475 else:
476
477=== modified file 'bin/addons/kit/wizard/kit_selection.py'
478--- bin/addons/kit/wizard/kit_selection.py 2014-07-22 12:11:45 +0000
479+++ bin/addons/kit/wizard/kit_selection.py 2017-10-02 12:34:11 +0000
480@@ -225,18 +225,7 @@
481 })
482 # copy existing sol
483 last_line_id = sol_obj.copy(cr, uid, last_line_id, values, context=ctx_keep_info)
484- # call the new procurement creation method
485- so_obj.action_ship_proc_create(cr, uid, [obj.corresponding_so_id_kit_selection.id], context=context)
486- # run the procurement, the make_po function detects the link to original po
487- # and force merge the line to this po (even if it is not draft anymore)
488- new_data_so = sol_obj.read(cr, uid, [last_line_id], ['procurement_id'], context=context)
489- new_proc_id = new_data_so[0]['procurement_id'][0]
490- wf_service.trg_validate(uid, 'procurement.order', new_proc_id, 'button_check', cr)
491- # if original po line is confirmed, we action_confirm new line
492- if obj.order_line_id_kit_selection.state == 'confirmed':
493- # the correct line number according to new line number policy is set in po_line_values_hook of order_line_number/order_line_number.py/procurement_order
494- new_po_ids = pol_obj.search(cr, uid, [('procurement_id', '=', new_proc_id)], context=context)
495- pol_obj.action_confirm(cr, uid, new_po_ids, context=context)
496+ # /* code deletion for partial confirmation US-3185 */
497 else:
498 # first item to be treated, we update the existing purchase order line
499 # sale order line will be updated when the Po is confirmed
500
501=== modified file 'bin/addons/msf_cross_docking/cross_docking.py'
502--- bin/addons/msf_cross_docking/cross_docking.py 2016-08-18 08:32:03 +0000
503+++ bin/addons/msf_cross_docking/cross_docking.py 2017-10-02 12:34:11 +0000
504@@ -39,7 +39,7 @@
505 _columns = {
506 'cross_docking_ok': fields.boolean('Cross docking'),
507 'location_id': fields.many2one('stock.location', 'Destination', required=True, domain=[('usage', '<>', 'view')],
508- help="""This location is set according to the Warehouse selected, or according to the option 'Cross docking'
509+ help="""This location is set according to the Warehouse selected, or according to the option 'Cross docking'
510 or freely if you do not select 'Warehouse'.But if the 'Order category' is set to 'Transport' or 'Service',
511 you cannot have an other location than 'Service'"""),
512 }
513@@ -75,7 +75,7 @@
514 warning = {
515 'title': _('Warning'),
516 'message': _('The IR lines to an internal location sourced by one of the lines of this PO will not affected by this modification'),
517- }
518+ }
519 else:
520 warehouse_obj = self.pool.get('stock.warehouse')
521 if not warehouse_id:
522@@ -113,16 +113,6 @@
523 res['value']['cross_docking_ok'] = cross_docking_ok
524 return res
525
526- def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, order_type, dest_address_id):
527- """ Set cross_docking_ok to False when we change warehouse.
528- @param warehouse_id: Changed id of warehouse.
529- @return: Dictionary of values.
530- """
531- res = super(purchase_order, self).onchange_warehouse_id(cr, uid, ids, warehouse_id, order_type, dest_address_id)
532- if warehouse_id:
533- res['value'].update({'cross_docking_ok': False})
534- return res
535-
536 def onchange_categ(self, cr, uid, ids, category, warehouse_id, cross_docking_ok, location_id, context=None):
537 """
538 Check if the list of products is valid for this new category
539@@ -654,7 +644,6 @@
540 todo.append(move.id)
541 self.infolog(cr, uid, "The source location of the stock move id:%s has been changed to cross-docking location" % (move.id))
542 ret = True
543- picking_todo = []
544 if todo:
545 ret = self.write(cr, uid, todo, {'location_id': cross_docking_location, 'move_cross_docking_ok': True}, context=context)
546
547@@ -688,7 +677,6 @@
548 ids = [ids]
549 obj_data = self.pool.get('ir.model.data')
550 todo = []
551- picking_todo = []
552 for move in self.browse(cr, uid, ids, context=context):
553 if move.state != 'done':
554 '''
555
556=== modified file 'bin/addons/msf_cross_docking/cross_docking_view.xml'
557--- bin/addons/msf_cross_docking/cross_docking_view.xml 2017-05-10 07:09:18 +0000
558+++ bin/addons/msf_cross_docking/cross_docking_view.xml 2017-10-02 12:34:11 +0000
559@@ -3,47 +3,7 @@
560 <data>
561
562 <!-- Here we just add a boolean in the page 'Delivery and invoicing' of the purchase order form notebook -->
563- <record id="purchase_cross_docking_form_view2" model="ir.ui.view">
564- <field name="name">purchase.cross.docking.form.view2</field>
565- <field name="model">purchase.order</field>
566- <field name="type">form</field>
567- <field name="priority">45</field>
568- <field name="inherit_id" ref="purchase_override.purchase_order_type_form_view" />
569- <field name="arch" type="xml">
570- <data>
571- <xpath expr="/form/group/field[@name='categ']" position="replace" >
572- <field name="categ" on_change="onchange_categ(categ, warehouse_id, cross_docking_ok, location_id)" />
573- </xpath>
574- </data>
575- </field>
576- </record>
577
578- <record id="purchase_cross_docking_form_view" model="ir.ui.view">
579- <field name="name">purchase.cross.docking.form.view</field>
580- <field name="model">purchase.order</field>
581- <field name="type">form</field>
582- <field name="priority">25</field>
583- <field name="inherit_id" ref="purchase.purchase_order_form" />
584- <field name="arch" type="xml">
585- <data>
586- <xpath expr="/form/notebook/page[@string='Delivery &amp; Invoicing']/group/field[@name='location_id']" position="before" >
587- <field name="cross_docking_ok"
588- attrs="{'readonly':['|', '|', '|', ('unallocation_ok', '=', True), ('order_type', '=', 'direct'), ('allocation_setup', '=', 'unallocated'), ('state', '!=', 'draft')]}"
589- on_change="onchange_cross_docking_ok(cross_docking_ok, warehouse_id, categ)"
590- />
591- </xpath>
592- <field name="location_id" position= "replace">
593- <!-- attrs="{'readonly':['|', ('state', 'in', ['sourced', 'split', 'rfq_updated', 'done', 'cancel', 'confirmed', 'approved', 'except_picking', 'except_invoice', 'confirmed_wait']), '|',('cross_docking_ok','=', True), ('categ', 'in', ['service', 'transport'])], 'invisible': [('order_type', '=', 'direct')]}"-->
594- <!-- [UF-1689] we want the user to select only Cross Docking or Input i.e. to tick or clear the checkbox cross docking
595- so we set the location_id always readonly-->
596- <field name="location_id"
597- domain="[('usage', '=', 'internal')]"
598- readonly="1"
599- on_change="onchange_location_id(location_id,categ)"/>
600- </field>
601- </data>
602- </field>
603- </record>
604
605 <!-- Here we add 'purchase_id' in the context for defining a 'default_get'
606 when we want to create a new stock move directly in the delivery form -->
607
608=== modified file 'bin/addons/msf_custom_settings/view/purchase_view.xml'
609--- bin/addons/msf_custom_settings/view/purchase_view.xml 2017-08-22 14:01:21 +0000
610+++ bin/addons/msf_custom_settings/view/purchase_view.xml 2017-10-02 12:34:11 +0000
611@@ -35,10 +35,10 @@
612 <field name="canceled_end" invisible="1" />
613 <field name="partner_id" on_change="onchange_partner_id(partner_id,date_order,est_transport_lead_time,context)" context="{'search_default_supplier':1}"
614 domain="[('supplier', '=', True), ('id', '!=', company_id), ('check_partner_po', '=', {'order_type':order_type, 'partner_id': partner_id, 'split_po': related_sourcing_id}), ('check_partner_rfq', '=', tender_id), ('partner_not_int', 'in', {'type':'po', 'ids':[active_id]})]"
615- attrs="{'readonly': [('state', 'in', ['sourced', 'split', 'rfq_sent', 'rfq_updated', 'done', 'cancel', 'confirmed', 'confirmed_wait', 'approved', 'except_picking', 'except_invoice'])]}" />
616+ attrs="{'readonly': [('state', 'in', ['sourced', 'split', 'rfq_sent', 'rfq_updated', 'done', 'cancel', 'validated', 'confirmed_wait', 'confirmed', 'except_picking', 'except_invoice'])]}" />
617 <field name="partner_type" />
618- <field name="partner_address_id" attrs="{'readonly': [('state', 'in', ['sourced', 'split', 'rfq_sent', 'rfq_updated', 'done', 'cancel', 'confirmed_wait', 'approved', 'except_picking', 'except_invoice'])]}"/>
619- <field name="origin" attrs="{'readonly': [('state', 'in', ['sourced', 'split', 'rfq_sent', 'rfq_updated', 'done', 'cancel', 'confirmed', 'confirmed_wait', 'approved', 'except_picking', 'except_invoice'])]}" widget="char"/>
620+ <field name="partner_address_id" attrs="{'readonly': [('state', 'in', ['sourced', 'split', 'rfq_sent', 'rfq_updated', 'done', 'cancel', 'confirmed_wait', 'confirmed', 'except_picking', 'except_invoice'])]}"/>
621+ <field name="origin" attrs="{'readonly': [('state', 'in', ['sourced', 'split', 'rfq_sent', 'rfq_updated', 'done', 'cancel', 'validated', 'confirmed_wait', 'confirmed', 'except_picking', 'except_invoice'])]}" widget="char"/>
622 <field name="internal_type" string="Zone" />
623 <field name="customer_ref" widget="char" />
624 <group colspan="2" col="3">
625@@ -47,7 +47,7 @@
626 attrs="{'invisible': [('state', 'not in', ('draft', 'confirmed'))]}" />
627 </group>
628 <field name="pricelist_id" string="Currency" domain="[('type', '=', 'purchase'), ('in_search', '=', partner_type)]"
629- colspan="3" attrs="{'readonly': [('state', 'in', ['rfq_updated', 'done', 'cancel', 'confirmed_wait', 'approved', 'except_picking', 'except_invoice'])]}"/>
630+ colspan="3" attrs="{'readonly': [('state', 'in', ['rfq_updated', 'done', 'cancel', 'confirmed_wait', 'confirmed', 'except_picking', 'except_invoice'])]}"/>
631 </group>
632 </xpath>
633 <xpath expr="/form/notebook" position="after">
634@@ -85,36 +85,37 @@
635 <newline/>
636
637 <group colspan="4" col="16">
638- <field name="state" readonly="1"/>
639+ <field name="state" attrs="{'invisible': [('rfq_ok', '=', True)]}" readonly="1"/>
640+ <field name="rfq_state" attrs="{'invisible': [('rfq_ok', '=', False)]}" readonly="1"/>
641 <field name="from_procurement" invisible="1" />
642- <button name="dpo_received" string="Validate the reception" icon="terp-camera_test"
643- attrs="{'invisible': ['|', ('state', '!=', 'approved'), ('order_type', '!=', 'direct')]}" />
644- <button name="purchase_cancel" type="object"
645- attrs="{'invisible': ['|', ('state', 'not in', ['draft', 'confirmed', 'rfq_sent']), '&amp;',('partner_type', 'not in', ('external', 'esc')), ('state', '=', 'confirmed')]}"
646+ <button name="close_lines" type="object" string="Validate the reception" icon="terp-camera_test"
647+ attrs="{'invisible': ['|', ('state', '!=', 'confirmed'), ('order_type', '!=', 'direct')]}" />
648+ <button name="purchase_cancel" type="object"
649+ attrs="{'invisible': ['|', ('rfq_state', 'not in', ['draft', 'validated', 'sent']), '&amp;',('partner_type', 'not in', ('external', 'esc')), ('state', '=', 'validated')]}"
650 string="Cancel" icon="gtk-cancel"/>
651 <button name="action_cancel_draft" states="cancel" string="Set to Draft" type="object" icon="gtk-convert" attrs="{'invisible': ['|', '|', '|', '|', '|', ('canceled_end', '=', True), ('po_from_fo', '=', True), ('po_from_ir', '=', True), ('po_from_rr', '=', True), ('tender_id', '!=', False), ('state', '!=', 'cancel')]}" />
652 <!-- UTP-469 − Remove the cancel button
653- <button name="action_cancel" states="approved,except_picking,except_invoice,wait" string="Cancel Purchase Order" type="object" icon="gtk-cancel" attrs="{'invisible': ['|', ('tender_id', '!=', False), ('state','not in', ['approved','except_picking','except_invoice','wait'])]}"/> -->
654+ <button name="action_cancel" states="approved,except_picking,except_invoice,wait" string="Cancel Purchase Order" type="object" icon="gtk-cancel" attrs="{'invisible': ['|', ('tender_id', '!=', False), ('state','not in', ['confirmed','except_picking','except_invoice','wait'])]}"/> -->
655 <button name="picking_ok" states="except_picking" string="Manually Corrected" icon="gtk-convert" attrs="{'invisible': ['|', ('tender_id', '!=', False), ('state', '!=', 'except_picking')]}"/>
656 <button name="invoice_ok" states="except_invoice" string="Manually Corrected" icon="gtk-convert" attrs="{'invisible': ['|', ('tender_id', '!=', False), ('state', '!=', 'except_invoice')]}"/>
657 <button name="purchase_appbuyer" states="wait_auth" string="Approve Purchase" icon="gtk-ok" attrs="{'invisible': ['|', ('tender_id', '!=', False), ('state', '!=', 'wait_auth')]}"/>
658- <button name="confirm_button" states="confirmed" string="Confirm" icon="gtk-go-forward" type="object" />
659- <button name="purchase_confirm" states="draft" string="Validate" icon="gtk-go-forward" attrs="{'invisible': ['|', ('rfq_ok', '!=', False), ('state', '!=', 'draft')]}" />
660- <button name="rfq_sent" type="object" states="draft" string="Send RfQ" icon="gtk-go-forward" attrs="{'invisible': ['|', ('rfq_ok', '=', False), ('state', '!=', 'draft')]}" />
661- <button name="check_rfq_updated" type="object" states="rfq_sent" string="RfQ Updated" icon="gtk-go-forward" attrs="{'invisible': ['|', ('rfq_ok', '=', False), ('state', '!=', 'rfq_sent')]}" />
662- <button name="generate_po_from_rfq" states="done" type="object" string="Generate Po" icon="gtk-dnd-multiple" attrs="{'invisible': ['|', '|', ('tender_id', '!=', False), ('state', '!=', 'rfq_updated'), ('from_procurement', '=', True)], 'readonly': [('amount_total', '=', 0.00)]}" />
663- <button name="rfq_done" states="rfq_updated" string="RfQ Closed" icon="gtk-go-forward" attrs="{'invisible': ['|', '|', ('tender_id', '!=', False), ('state', '!=', 'rfq_updated'), ('from_procurement', '=', True)]}" />
664- <button name="rfq_done" states="rfq_updated" string="Continue sourcing" icon="gtk-go-forward" attrs="{'invisible': ['|', '|', ('tender_id', '!=', False), ('state', '!=', 'rfq_updated'), ('from_procurement', '=', False)]}" />
665+ <button name="validate_lines" states="draft,draft_p" string="Validate lines" type="object" icon="gtk-go-forward"/>
666+ <button name="confirm_lines" states="validated,validated_p" string="Confirm lines" icon="gtk-go-forward" type="object" />
667+ <button name="rfq_sent" type="object" string="Send RfQ" icon="gtk-go-forward" attrs="{'invisible': ['|', ('rfq_ok', '=', False), ('rfq_state', '!=', 'draft')]}" />
668+ <button name="check_rfq_updated" type="object" string="RfQ Updated" icon="gtk-go-forward" attrs="{'invisible': ['|', ('rfq_ok', '=', False), ('rfq_state', '!=', 'sent')]}" />
669+ <button name="generate_po_from_rfq" type="object" string="Generate Po" icon="gtk-dnd-multiple" attrs="{'invisible': ['|', '|', ('tender_id', '!=', False), ('rfq_state', '!=', 'updated'), ('from_procurement', '=', True)], 'readonly': [('amount_total', '=', 0.00)]}" />
670+ <button name="rfq_done" string="RfQ Closed" icon="gtk-go-forward" attrs="{'invisible': ['|', '|', ('tender_id', '!=', False), ('rfq_state', '!=', 'updated'), ('from_procurement', '=', True)]}" />
671+ <button name="rfq_done" string="Continue sourcing" icon="gtk-go-forward" attrs="{'invisible': ['|', '|', ('tender_id', '!=', False), ('rfq_state', '!=', 'updated'), ('from_procurement', '=', False)]}" />
672 </group>
673 </xpath>
674 <xpath expr="/form//field[@name='pricelist_id']" position="replace">
675 <group colspan="2" col="3">
676 <field name="no_line" invisible="1" />
677 <field name="pricelist_id" string="Currency" colspan="2"
678- attrs="{'readonly': ['|', ('no_line', '=', False), ('state', 'in', ['rfq_updated', 'done', 'cancel', 'confirmed_wait', 'approved', 'except_picking', 'except_invoice'])]}"
679+ attrs="{'readonly': ['|', ('no_line', '=', False), ('state', 'in', ['rfq_updated', 'done', 'cancel', 'confirmed_wait', 'confirmed', 'except_picking', 'except_invoice'])]}"
680 domain="[('type', '=', 'purchase'), ('in_search', '=', partner_type)]" />
681 <button name="change_currency" string="Change currency" icon="terp-dolar" type="object"
682- attrs="{'invisible': ['|', ('no_line', '=', True), ('state', 'in', ['rfq_updated', 'done', 'cancel', 'confirmed_wait', 'approved', 'except_picking', 'except_invoice'])]}" />
683+ attrs="{'invisible': ['|', ('no_line', '=', True), ('state', 'in', ['rfq_updated', 'done', 'cancel', 'confirmed_wait', 'confirmed', 'except_picking', 'except_invoice'])]}" />
684 </group>
685 <button colspan="4" string="Round Qty to SoQ" type="object" name="round_to_soq" icon="gtk-execute" attrs="{'invisible': [('state', '!=', 'draft')]}" />
686 </xpath>
687
688=== modified file 'bin/addons/msf_custom_settings/view/sale_view.xml'
689--- bin/addons/msf_custom_settings/view/sale_view.xml 2017-08-01 15:45:31 +0000
690+++ bin/addons/msf_custom_settings/view/sale_view.xml 2017-10-02 12:34:11 +0000
691@@ -53,7 +53,7 @@
692 <button name="invoice_cancel" states="invoice_except" string="Cancel Order" icon="gtk-cancel"/>
693 <button name="order_confirm" states="validated" string="Confirm Order" icon="gtk-apply" invisible="True" />
694 <button name="do_order_confirm_method" type="object" states="validated" string="Confirm Order" icon="gtk-apply" confirm="You are about to confirm the Field Order without going through the sourcing tool. Are you sure?" attrs="{'readonly': [('no_line', '=', True)]}" />
695- <button name="order_validated" string="Validate Order" icon="terp-check" states="draft" attrs="{'readonly': [('no_line', '=', True)]}" />
696+ <button name="validate_lines" type="object" string="Validate Lines" icon="terp-check" states="draft,draft_p" attrs="{'readonly': [('no_line', '=', True)]}" />
697 </group>
698 </xpath>
699 <xpath expr="/form//field[@name='pricelist_id']" position="replace">
700
701=== modified file 'bin/addons/msf_doc_import/__openerp__.py'
702--- bin/addons/msf_doc_import/__openerp__.py 2017-05-22 16:04:34 +0000
703+++ bin/addons/msf_doc_import/__openerp__.py 2017-10-02 12:34:11 +0000
704@@ -70,8 +70,6 @@
705 'wizard/wiz_common_import_view.xml',
706 'data/msf_doc_import_data.xml',
707 'data/inactive_categ.xml',
708- 'workflow/purchase_workflow.xml',
709- 'workflow/sale_workflow.xml',
710 'workflow/tender_flow_workflow.xml',
711 'workflow/procurement_request_workflow.xml',
712 'doc_import_report.xml',
713
714=== modified file 'bin/addons/msf_doc_import/view/purchase_order_import_line_view.xml'
715--- bin/addons/msf_doc_import/view/purchase_order_import_line_view.xml 2017-01-16 14:20:41 +0000
716+++ bin/addons/msf_doc_import/view/purchase_order_import_line_view.xml 2017-10-02 12:34:11 +0000
717@@ -42,9 +42,6 @@
718 </field>
719 </xpath>
720 <xpath expr="/form/notebook/page[@string='Purchase Order']/field[@name='order_line']" position="before" >
721- <button name="wizard_import_po_line" string="Import RfQ lines" icon="gtk-dnd" type="object" attrs="{'invisible':[('state', '!=', 'rfq_sent')]}"/>
722- </xpath>
723- <xpath expr="/form/notebook/page[@string='Purchase Order']/field[@name='order_line']" position="before" >
724 <group name="import" string=" Import Lines " colspan="1" col="4" attrs="{'invisible':[('state', '!=', 'draft')]}">
725 <button name="wizard_import_po_line" string="Import lines" icon="gtk-dnd" col="1" type="object" attrs="{'invisible':[('state', '!=', 'draft')]}"/>
726 <button name="button_remove_lines" string="Delete Lines" icon="gtk-remove" colspan="1" type="object" attrs="{'invisible': ['|', ('po_from_fo', '=', True), ('po_from_ir', '=', True)]}" />
727@@ -52,8 +49,60 @@
728 <button name="check_lines_to_fix" string="Check Lines" icon="gtk-dialog-warning" colspan="1" type="object" context="{'rfq_ok': rfq_ok}"/>
729 <button name="add_multiple_lines" string="Add multiple lines" icon="gtk-add" colspan="4" type="object" />
730 </group>
731- <button name="wizard_import_file" string="Import PO confirmation" icon="gtk-execute" colspan="1" type="object" attrs="{'invisible':[('state', '!=', 'confirmed')]}"/>
732- <button name="export_po_integration" string="Export PO Validated" icon="gtk-execute" colspan="1" type="object" attrs="{'invisible':[('state', '!=', 'confirmed')]}"/>
733+ <button name="wizard_import_file" string="Import PO confirmation" icon="gtk-execute" colspan="1" type="object" attrs="{'invisible':[('state', 'not in', ['validated', 'validated_n', 'validated_p'])]}"/>
734+ <button name="export_po_integration" string="Export PO Validated" icon="gtk-execute" colspan="1" type="object" attrs="{'invisible':[('state', 'not in', ['validated', 'validated_n', 'validated_p'])]}"/>
735+ </xpath>
736+ </data>
737+ </field>
738+ </record>
739+
740+ <record id="rfq_form_import" model="ir.ui.view">
741+ <field name="name">rfq.form.import</field>
742+ <field name="model">purchase.order</field>
743+ <field name="type">form</field>
744+ <field name="priority">110</field>
745+ <field name="inherit_id" ref="tender_flow.view_rfq_form" />
746+ <field name="arch" type="xml">
747+ <data>
748+ <xpath expr="/form" position="attributes">
749+ <attribute name="noteditable">import_in_progress==True or update_in_progress==True</attribute>
750+ </xpath>
751+ <xpath expr="/form//field" position="before">
752+ <field name="update_in_progress" invisible="1" readonly="1" />
753+ </xpath>
754+
755+ <xpath expr="/form/notebook/page/field[@name='order_line']/tree/field[@name='currency_id']" position="after">
756+ <field name="to_correct_ok" invisible="1"/>
757+ </xpath>
758+ <xpath expr="/form/notebook/page/field[@name='order_line']/tree//field[@name='default_name']" position="after">
759+ <field name="inactive_product" invisible="1" />
760+ <field name="soq_updated" invisible="1" readonly="1" />
761+ </xpath>
762+ <xpath expr="/form/notebook/page/field[@name='order_line']/tree" position="attributes" >
763+ <attribute name="colors">red:to_correct_ok == True or inactive_product == True or red_color == True; #C8C8C8: fake_state == 'cancel'; orange:product_qty == 0.00;blue: soq_updated == True</attribute>
764+ </xpath>
765+ <xpath expr="/form/notebook/page[@string='Notes']/field[@name='notes']" position="before">
766+ <field name="import_in_progress" readonly="1" invisible="0" />
767+ </xpath>
768+ <xpath expr="/form/notebook/page[@string='Notes']/field[@name='project_ref']" position="after">
769+ <separator colspan="4" string="Sourcing group" />
770+ <field name="related_sourcing_id" readonly="True" />
771+ <separator colspan="4" string="Import filenames" />
772+ <field name="import_filenames" mode="tree" readonly="1" nolabel="1" colspan="4">
773+ <tree string="Imported filenames">
774+ <field name="timestamp" />
775+ <field name="filename" />
776+ </tree>
777+ </field>
778+ </xpath>
779+ <xpath expr="/form/notebook/page/field[@name='order_line']" position="before" >
780+ <group name="import" string=" Import Lines " colspan="4" attrs="{'invisible':[('rfq_state', '!=', 'draft')]}">
781+ <button name="wizard_import_po_line" string="Import lines" icon="gtk-dnd" col="1" type="object"/>
782+ <button name="button_remove_lines" string="Delete Lines" icon="gtk-remove" colspan="1" type="object" attrs="{'invisible': ['|', ('po_from_fo', '=', True), ('po_from_ir', '=', True)]}" />
783+ <button name="button_remove_lines" string="Delete Lines" confirm="This document originated from an IR/FO, if you delete these products now, they will all be deleted. If you wish to Cancel &amp; Resource them, please do this line by line." icon="gtk-remove" colspan="1" type="object" attrs="{'invisible': [('po_from_fo', '=', False), ('po_from_ir', '=', False)]}" />
784+ <button name="check_lines_to_fix" string="Check Lines" icon="gtk-dialog-warning" colspan="1" type="object" context="{'rfq_ok': rfq_ok}"/>
785+ <button name="add_multiple_lines" string="Add multiple lines" icon="gtk-add" colspan="4" type="object" />
786+ </group>
787 </xpath>
788 </data>
789 </field>
790
791=== modified file 'bin/addons/msf_doc_import/wizard/wizard_import_po_line.py'
792--- bin/addons/msf_doc_import/wizard/wizard_import_po_line.py 2016-12-06 11:15:12 +0000
793+++ bin/addons/msf_doc_import/wizard/wizard_import_po_line.py 2017-10-02 12:34:11 +0000
794@@ -163,6 +163,7 @@
795 'default_code': False,
796 'confirmed_delivery_date': False,
797 'line_number': '',
798+ 'set_as_validated_n': True,
799 }
800
801 col_count = len(row)
802
803=== modified file 'bin/addons/msf_doc_import/wizard/wizard_po_simulation_screen.py'
804--- bin/addons/msf_doc_import/wizard/wizard_po_simulation_screen.py 2016-12-05 09:13:30 +0000
805+++ bin/addons/msf_doc_import/wizard/wizard_po_simulation_screen.py 2017-10-02 12:34:11 +0000
806@@ -29,6 +29,7 @@
807 from lxml.etree import XMLSyntaxError
808 import logging
809 import os
810+import netsvc
811
812 from mx import DateTime
813
814@@ -1540,6 +1541,7 @@
815 line_obj = self.pool.get('purchase.order.line')
816 split_obj = self.pool.get('split.purchase.order.line.wizard')
817 simu_obj = self.pool.get('wizard.import.po.simulation.screen')
818+ wf_service = netsvc.LocalService("workflow")
819
820 if context is None:
821 context = {}
822@@ -1561,7 +1563,7 @@
823 # Don't do anything
824 continue
825 elif line.type_change == 'del' and line.po_line_id:
826- line_obj.fake_unlink(cr, uid, [line.po_line_id.id], context=context)
827+ wf_service.trg_validate(uid, 'purchase.order.line', line.po_line_id.id, 'cancel', cr)
828 elif line.type_change == 'split' and line.parent_line_id:
829 # Call the split line wizard
830 po_line_id = False
831@@ -1596,6 +1598,7 @@
832 line_vals = {'product_uom': line.imp_uom.id,
833 'product_id': line.imp_product_id.id,
834 'price_unit': line.imp_price,
835+ 'set_as_validated_n': True,
836 }
837 if line.imp_drd:
838 line_vals['date_planned'] = line.imp_drd
839@@ -1641,6 +1644,7 @@
840 'price_unit': line.imp_price,
841 'product_qty': line.imp_qty,
842 'date_planned': line.imp_drd or line.simu_id.order_id.delivery_requested_date,
843+ 'set_as_validated_n': True,
844 }
845 if line.imp_dcd:
846 line_vals['confirmed_delivery_date'] = line.imp_dcd
847
848=== modified file 'bin/addons/msf_doc_import/workflow/procurement_request_workflow.xml'
849--- bin/addons/msf_doc_import/workflow/procurement_request_workflow.xml 2012-07-20 13:52:00 +0000
850+++ bin/addons/msf_doc_import/workflow/procurement_request_workflow.xml 2017-10-02 12:34:11 +0000
851@@ -7,11 +7,13 @@
852
853 <!-- We redefined this transition to enhance the coverage of the condition -->
854
855+<!--
856 <record id="procurement_request.trans_proc_validate" model="workflow.transition">
857 <field name="act_from" ref="procurement_request.act_procurement"/>
858 <field name="act_to" ref="procurement_request.act_proc_validate"/>
859 <field name="signal">procurement_validate</field>
860 <field name="condition">procurement_request==True and check_lines_to_fix()</field>
861 </record>
862+-->
863 </data>
864-</openerp>
865\ No newline at end of file
866+</openerp>
867
868=== removed file 'bin/addons/msf_doc_import/workflow/purchase_workflow.xml'
869--- bin/addons/msf_doc_import/workflow/purchase_workflow.xml 2013-05-08 08:57:30 +0000
870+++ bin/addons/msf_doc_import/workflow/purchase_workflow.xml 1970-01-01 00:00:00 +0000
871@@ -1,11 +0,0 @@
872-<?xml version="1.0" encoding="utf-8"?>
873-<openerp>
874- <data>
875- <record id="purchase.trans_draft_confirmed" model="workflow.transition">
876- <field name="condition">check_lines_to_fix()</field>
877- </record>
878- <record id="purchase.trans_confirmed_cancel" model="workflow.transition">
879- <field name="condition">check_condition()</field>
880- </record>
881- </data>
882-</openerp>
883\ No newline at end of file
884
885=== removed file 'bin/addons/msf_doc_import/workflow/sale_workflow.xml'
886--- bin/addons/msf_doc_import/workflow/sale_workflow.xml 2012-07-20 13:52:00 +0000
887+++ bin/addons/msf_doc_import/workflow/sale_workflow.xml 1970-01-01 00:00:00 +0000
888@@ -1,8 +0,0 @@
889-<?xml version="1.0" encoding="utf-8"?>
890-<openerp>
891- <data>
892- <record id="sale_override.trans_draft_validated" model="workflow.transition">
893- <field name="condition">check_lines_to_fix()</field>
894- </record>
895- </data>
896-</openerp>
897\ No newline at end of file
898
899=== modified file 'bin/addons/msf_order_date/__openerp__.py'
900--- bin/addons/msf_order_date/__openerp__.py 2012-02-27 16:26:55 +0000
901+++ bin/addons/msf_order_date/__openerp__.py 2017-10-02 12:34:11 +0000
902@@ -23,11 +23,10 @@
903 "name": "MSF Order dates",
904 "version": "3.0",
905 "depends": ["base",
906- "sale",
907 "purchase",
908 "account",
909 "stock_override",
910- "sale_override",
911+ "sale",
912 "purchase_override",
913 "partner_modification",
914 "msf_tools",
915
916=== modified file 'bin/addons/msf_order_date/order_dates.py'
917--- bin/addons/msf_order_date/order_dates.py 2017-08-25 15:37:51 +0000
918+++ bin/addons/msf_order_date/order_dates.py 2017-10-02 12:34:11 +0000
919@@ -32,7 +32,7 @@
920 from msf_order_date import TRANSPORT_TYPE
921 from msf_order_date import ZONE_SELECTION
922 from purchase_override import PURCHASE_ORDER_STATE_SELECTION
923-from sale_override import SALE_ORDER_STATE_SELECTION
924+from sale import SALE_ORDER_STATE_SELECTION
925
926 class res_partner(osv.osv):
927 _name = 'res.partner'
928@@ -437,13 +437,7 @@
929 if context is None:
930 context = {}
931
932- order_id = context.get('active_id', [])
933- if isinstance(order_id, (int, long)):
934- order_id = [order_id]
935-
936-
937 return {'value': {'date_planned': requested_date, }}
938-# 'confirmed_delivery_date': confirmed_date}}
939
940 def common_create(self, cr, uid, data, type, context=None):
941 '''
942@@ -929,6 +923,8 @@
943
944 for order in self.browse(cr, uid, ids, context=context):
945 # Fill partner type
946+ if data.get('partner_id') and isinstance(data.get('partner_id'), basestring):
947+ data['partner_id'] = int(data.get('partner_id'))
948 partner = self.pool.get('res.partner').browse(cr, uid, data.get('partner_id', order.partner_id.id), context=context)
949 # partner type - always set
950 data.update({'partner_type': partner.partner_type, })
951
952=== modified file 'bin/addons/msf_order_date/order_dates_view.xml'
953--- bin/addons/msf_order_date/order_dates_view.xml 2017-08-01 15:45:31 +0000
954+++ bin/addons/msf_order_date/order_dates_view.xml 2017-10-02 12:34:11 +0000
955@@ -36,20 +36,20 @@
956 <group colspan="4" col="4">
957 <separator colspan="4" string="Dates" />
958 <group colspan="2" col="3">
959- <field name="delivery_requested_date" attrs="{'readonly': [('state', 'not in', ('draft', 'confirmed'))]}"
960+ <field name="delivery_requested_date" attrs="{'readonly': [('state', 'not in', ('draft', 'validated'))]}"
961 on_change="onchange_requested_date(partner_id,date_order,delivery_requested_date,est_transport_lead_time, order_type, context)" />
962 <button colspan="1" name="update_date" string="Update Lines" type="object" context="{'field_name': 'requested', 'type': 'purchase.order'}" icon="gtk-indent"
963- attrs="{'invisible': [('state', 'not in', ('draft', 'confirmed'))]}" />
964+ attrs="{'invisible': [('state', 'not in', ('draft', 'validated'))]}" />
965 </group>
966 <group colspan="2" col="3">
967- <field name="delivery_confirmed_date" attrs="{'readonly': [('state', 'not in', ('draft', 'confirmed'))]}" />
968+ <field name="delivery_confirmed_date" attrs="{'readonly': [('state', 'not in', ('draft', 'validated'))]}" />
969 <button colspan="1" name="update_date" string="Update Lines" type="object" context="{'field_name': 'confirmed', 'type': 'purchase.order'}" icon="gtk-indent"
970- attrs="{'invisible': [('state', 'not in', ('draft', 'confirmed'))]}" />
971+ attrs="{'invisible': [('state', 'not in', ('draft', 'validated'))]}" />
972 </group>
973 <field name="transport_type"
974 on_change="onchange_transport_type(partner_id,transport_type,delivery_requested_date,context)"
975- attrs="{'readonly': [('state', 'not in', ('draft', 'confirmed'))]}" />
976- <field name="est_transport_lead_time" attrs="{'readonly': [('state', 'not in', ('draft', 'confirmed'))]}" />
977+ attrs="{'readonly': [('state', 'not in', ('draft', 'validated'))]}" />
978+ <field name="est_transport_lead_time" attrs="{'readonly': [('state', 'not in', ('draft', 'validated'))]}" />
979 <field name="ready_to_ship_date" attrs="{'readonly': ['|', ('shipped', '=', True), ('state', 'in', ['split', 'sourced'])], 'invisible': [('partner_type', '=', 'external')]}" />
980 <field name="shipment_date" attrs="{'readonly': ['|', ('shipped', '=', True), ('state', 'in', ['split', 'sourced'])], 'invisible': [('partner_type', '=', 'external')]}" />
981 <field name="arrival_date" attrs="{'readonly': ['|', ('shipped_rate', '!=', 0.0), ('state', '=', 'done')], 'invisible': [('internal_type', '!=', 'international')]}" />
982@@ -60,7 +60,7 @@
983 <xpath expr="/form//field[@name='order_type']" position="replace">
984 <field name="order_type"
985 on_change="onchange_internal_type(order_type, partner_id, categ, dest_partner_id, warehouse_id,delivery_requested_date)"
986- attrs="{'readonly': ['|', ('push_fo', '=', True), ('state', 'not in', ['draft', 'confirmed'])]}"
987+ attrs="{'readonly': ['|', ('push_fo', '=', True), ('state', 'not in', ['draft', 'validated'])]}"
988 />
989 </xpath>
990
991@@ -150,7 +150,7 @@
992
993 <xpath expr="/form/notebook//field[@name='order_line']/form/notebook//field[@name='delay']" position="replace">
994 <field name="so_state_stored" invisible="True" />
995- <field name="date_planned" attrs="{'readonly': [('so_state_stored', 'not in', ['draft', 'validated'])]}" />
996+ <field name="date_planned" attrs="{'readonly': [('state', 'not in', ['draft', 'validated'])]}" />
997 <field name="confirmed_delivery_date" attrs="{'readonly': [('so_state_stored', 'in', ('cancel','done'))]}" />
998 <field name="stock_take_date" />
999 </xpath>
1000
1001=== modified file 'bin/addons/msf_outgoing/msf_outgoing.py'
1002--- bin/addons/msf_outgoing/msf_outgoing.py 2017-09-03 15:15:05 +0000
1003+++ bin/addons/msf_outgoing/msf_outgoing.py 2017-10-02 12:34:11 +0000
1004@@ -1663,6 +1663,10 @@
1005 # UF-1617: set the flag to this packing object to indicate that the SHIP has been done, for synchronisation purpose
1006 cr.execute('update stock_picking set already_shipped=\'t\' where id=%s' % packing.id)
1007
1008+ # closing FO lines:
1009+ for stock_move in packing.move_lines:
1010+ if stock_move.sale_line_id:
1011+ wf_service.trg_validate(uid, 'sale.order.line', stock_move.sale_line_id.id, 'done', cr)
1012
1013 # Create automatically the invoice
1014 self.shipment_create_invoice(cr, uid, shipment.id, context=context)
1015@@ -3412,6 +3416,8 @@
1016 # Update the original move
1017 move_obj.write(cr, uid, [move.id], values, context=context)
1018 processed_moves.append(move.id)
1019+ if move.sale_line_id:
1020+ wf_service.trg_validate(uid, 'sale.order.line', move.sale_line_id.id, 'done', cr)
1021
1022 if not len(move_data):
1023 pick_type = 'Internal picking'
1024@@ -4967,11 +4973,11 @@
1025 Confirm or check the procurement order associated to the stock move
1026 '''
1027 pol_obj = self.pool.get('purchase.order.line')
1028- proc_obj = self.pool.get('procurement.order')
1029 pick_obj = self.pool.get('stock.picking')
1030 sol_obj = self.pool.get('sale.order.line')
1031 uom_obj = self.pool.get('product.uom')
1032 solc_obj = self.pool.get('sale.order.line.cancel')
1033+ wf_service = netsvc.LocalService("workflow")
1034
1035 if context is None:
1036 context = {}
1037@@ -5021,8 +5027,10 @@
1038
1039 diff_qty = uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, sol.product_uom.id)
1040 if move.has_to_be_resourced or move.picking_id.has_to_be_resourced:
1041- sol_obj.add_resource_line(cr, uid, sol.id, False, diff_qty, context=context)
1042- sol_obj.update_or_cancel_line(cr, uid, sol.id, diff_qty, context=context)
1043+ sol_obj.add_resource_line(cr, uid, sol.id, sol.order_id.id, diff_qty, context=context)
1044+ sol_obj.update_or_cancel_line(cr, uid, sol.id, diff_qty, resource=move.has_to_be_resourced,context=context)
1045+ signal = 'cancel_r' if move.has_to_be_resourced else 'cancel'
1046+ wf_service.trg_validate(uid, 'purchase.order.line', move.purchase_line_id.id, signal, cr)
1047
1048 # Cancel the remaining OUT line
1049 if diff_qty < sol.product_uom_qty:
1050@@ -5041,21 +5049,9 @@
1051 diff_qty = uom_obj._compute_qty(cr, uid, move.product_uom.id, move.product_qty, move.sale_line_id.product_uom.id)
1052 if diff_qty:
1053 if move.has_to_be_resourced or move.picking_id.has_to_be_resourced:
1054- sol_obj.add_resource_line(cr, uid, move.sale_line_id.id, False, diff_qty, context=context)
1055+ sol_obj.add_resource_line(cr, uid, move.sale_line_id.id, move.sale_line_id.order_id.id, diff_qty, context=context)
1056 if move.id not in context.get('not_resource_move', []):
1057- sol_obj.update_or_cancel_line(cr, uid, move.sale_line_id.id, diff_qty, context=context)
1058- if move.sale_line_id.procurement_id:
1059- # Search OUT moves that have the same source and there are done
1060- other_out_move_ids = self.search(cr, uid, [
1061- ('sale_line_id', '=', move.sale_line_id.id),
1062- ('state', 'in', ['assigned', 'confirmed', 'done']),
1063- ('id', '!=', move.id),
1064- ], context=context)
1065- if other_out_move_ids:
1066- proc_obj.write(cr, uid,
1067- [move.sale_line_id.procurement_id.id],
1068- {'move_id': other_out_move_ids[0]},
1069- context=context)
1070+ sol_obj.update_or_cancel_line(cr, uid, move.sale_line_id.id, diff_qty, resource=move.has_to_be_resourced, context=context)
1071
1072 self.action_done(cr, uid, move_to_done, context=context)
1073
1074@@ -5063,16 +5059,6 @@
1075 ids = self.search(cr, uid, [('id', 'in', ids)])
1076 res = super(stock_move, self).action_cancel(cr, uid, ids, context=context)
1077
1078- wf_service = netsvc.LocalService("workflow")
1079-
1080- proc_obj = self.pool.get('procurement.order')
1081- proc_ids = proc_obj.search(cr, uid, [('move_id', 'in', ids)], context=context)
1082- for proc in proc_obj.read(cr, uid, proc_ids, ['state'], context=context):
1083- if proc['state'] == 'draft':
1084- wf_service.trg_validate(uid, 'procurement.order', proc['id'], 'button_confirm', cr)
1085- else:
1086- wf_service.trg_validate(uid, 'procurement.order', proc['id'], 'button_check', cr)
1087-
1088 for ptc in pick_obj.browse(cr, uid, list(pick_to_check), context=context):
1089 if ptc.subtype == 'picking' and ptc.state == 'draft' and not pick_obj.has_picking_ticket_in_progress(cr, uid, [ptc.id], context=context)[ptc.id] and all(m.state == 'cancel' or m.product_qty == 0.00 for m in ptc.move_lines):
1090 moves_to_done = self.search(cr, uid, [('picking_id', '=', ptc.id), ('product_qty', '=', 0.00), ('state', 'not in', ['done', 'cancel'])], context=context)
1091
1092=== modified file 'bin/addons/msf_outgoing/msf_outgoing_view.xml'
1093--- bin/addons/msf_outgoing/msf_outgoing_view.xml 2017-05-23 15:47:57 +0000
1094+++ bin/addons/msf_outgoing/msf_outgoing_view.xml 2017-10-02 12:34:11 +0000
1095@@ -353,7 +353,7 @@
1096 <field name="has_draft_moves" invisible="1" />
1097 <button name="action_confirm_moves" states="draft" string="Confirm" type="object" icon="gtk-apply" attrs="{'invisible': [('has_draft_moves', '=', False)]}"/>
1098 <button name="action_assign" states="draft,confirmed,assigned" string="Check Availability" type="object" icon="gtk-find" context="{'from_button': True}"/>
1099- <button name="convert_to_standard" states="draft,assigned" string="Convert to Simple Out" type="object" icon="gtk-convert" context="{'from_button': True}" />
1100+ <button name="convert_to_standard" states="draft" string="Convert to Simple Out" type="object" icon="gtk-convert" context="{'from_button': True}" />
1101 <button name="create_picking" states="draft" string="Create Picking..." type="object" icon="gtk-add" help="Non available quantities will not be moved and will remain in the backorder document (unless you 'force availability' if you are sure goods are available)" context="{'from_button': True}" />
1102 <button name="force_assign" states="confirmed" string="Force Availability" type="object" icon="gtk-jump-to" context="{'from_button': True}" />
1103 <button name="validate_picking" states="assigned" string="Validate Picking..." type="object" icon="gtk-apply" context="{'from_button': True}" />
1104
1105=== modified file 'bin/addons/msf_processes/process/procurement_process.xml'
1106--- bin/addons/msf_processes/process/procurement_process.xml 2012-06-07 15:50:33 +0000
1107+++ bin/addons/msf_processes/process/procurement_process.xml 2017-10-02 12:34:11 +0000
1108@@ -104,7 +104,7 @@
1109 </record>
1110
1111 <!-- transitions -->
1112-
1113+<!--
1114 <record id="procurement.process_transition_draft_confirmed" model="process.transition">
1115 <field eval="[(6,0,[])]" name="transition_ids"/>
1116 <field eval="&quot;&quot;&quot;Confirm Procurement Order&quot;&quot;&quot;" name="name"/>
1117@@ -113,7 +113,7 @@
1118 <field model="process.node" name="target_node_id" ref="procurement.process_node_procure_confirmed"/>
1119 <field eval="[(6,0,[ref('sale.trans_draft_router')])]" name="transition_ids"/>
1120 </record>
1121-
1122+
1123 <record id="procurement.process_transition_confirmed_exception" model="process.transition">
1124 <field eval="[(6,0,[])]" name="transition_ids"/>
1125 <field eval="&quot;&quot;&quot;The Procurement Order goes in exception&quot;&quot;&quot;" name="name"/>
1126@@ -176,7 +176,7 @@
1127 <field model="process.node" name="target_node_id" ref="procurement.process_node_procure_po"/>
1128 <field eval="[(6,0,[ref('sale.trans_draft_router')])]" name="transition_ids"/>
1129 </record>
1130-
1131+
1132 <record id="procurement.process_transition_po_in" model="process.transition">
1133 <field eval="[(6,0,[])]" name="transition_ids"/>
1134 <field eval="&quot;&quot;&quot;Incoming Products is created.&quot;&quot;&quot;" name="name"/>
1135@@ -185,7 +185,7 @@
1136 <field model="process.node" name="target_node_id" ref="procurement.process_node_procure_in"/>
1137 <field eval="[(6,0,[ref('sale.trans_draft_router')])]" name="transition_ids"/>
1138 </record>
1139-
1140+-->
1141 <!-- Process process Service -->
1142
1143 <record id="procurement.process_process_serviceproductprocess0" model="process.process">
1144
1145=== modified file 'bin/addons/msf_processes/process/purchase_process.xml'
1146--- bin/addons/msf_processes/process/purchase_process.xml 2012-07-05 14:39:47 +0000
1147+++ bin/addons/msf_processes/process/purchase_process.xml 2017-10-02 12:34:11 +0000
1148@@ -171,7 +171,7 @@
1149 <!--
1150 Process Transition
1151 -->
1152-
1153+<!--
1154 <record id="purchase.process_transition_confirmingpurchaseorder0" model="process.transition">
1155 <field eval="[(6,0,[])]" name="transition_ids"/>
1156 <field eval="&quot;&quot;&quot;Confirmation&quot;&quot;&quot;" name="name"/>
1157@@ -180,7 +180,7 @@
1158 <field model="process.node" name="source_node_id" ref="purchase.process_node_draftpurchaseorder0"/>
1159 <field eval="[(6,0,[ref('purchase.trans_draft_confirmed')])]" name="transition_ids"/>
1160 </record>
1161-
1162+-->
1163 <record id="purchase.process_transition_confirmingpurchaseorder1" model="process.transition">
1164 <field eval="[(6,0,[])]" name="transition_ids"/>
1165 <field eval="&quot;&quot;&quot;Confirmation&quot;&quot;&quot;" name="name"/>
1166@@ -265,7 +265,7 @@
1167 <!--
1168 Process Action
1169 -->
1170-
1171+<!--
1172 <record id="purchase.process_transition_action_confirmpurchaseorder0" model="process.transition.action">
1173 <field eval="&quot;&quot;&quot;wkf_confirm_order&quot;&quot;&quot;" name="action"/>
1174 <field eval="&quot;&quot;&quot;object&quot;&quot;&quot;" name="state"/>
1175@@ -293,7 +293,7 @@
1176 <field eval="&quot;&quot;&quot;Cancel&quot;&quot;&quot;" name="name"/>
1177 <field name="transition_id" ref="purchase.process_transition_approvingpurchaseorder0"/>
1178 </record>
1179-<!--
1180+
1181 <record id="purchase.process_transition_action_invoicefrompurchaseorder0" model="process.transition.action">
1182 <field eval="&quot;&quot;&quot;action_invoice_create&quot;&quot;&quot;" name="action"/>
1183 <field eval="&quot;&quot;&quot;object&quot;&quot;&quot;" name="state"/>
1184
1185=== modified file 'bin/addons/msf_processes/process/sale_process.xml'
1186--- bin/addons/msf_processes/process/sale_process.xml 2012-06-07 15:50:33 +0000
1187+++ bin/addons/msf_processes/process/sale_process.xml 2017-10-02 12:34:11 +0000
1188@@ -210,7 +210,7 @@
1189 <!--
1190 Process Transition
1191 -->
1192-
1193+<!--
1194 <record id="sale.process_transition_validatequotation0" model="process.transition">
1195 <field eval="[(6,0,[])]" name="transition_ids"/>
1196 <field eval="&quot;&quot;&quot;Confirm Field Order&quot;&quot;&quot;" name="name"/>
1197@@ -237,7 +237,7 @@
1198 <field model="process.node" name="target_node_id" ref="sale.process_node_saleorder_done"/>
1199 <field eval="[(6,0,[ref('sale.trans_draft_router')])]" name="transition_ids"/>
1200 </record>
1201-
1202+-->
1203 <record id="sale.process_transition_saleprocurement0" model="process.transition">
1204 <field eval="[(6,0,[])]" name="transition_ids"/>
1205 <field eval="&quot;&quot;&quot;Create Procurement Order&quot;&quot;&quot;" name="name"/>
1206@@ -308,7 +308,7 @@
1207 <!--
1208 Process Action
1209 -->
1210-
1211+<!--
1212 <record id="sale.process_transition_action_confirm0" model="process.transition.action">
1213 <field eval="&quot;&quot;&quot;action_wait&quot;&quot;&quot;" name="action"/>
1214 <field eval="&quot;&quot;&quot;object&quot;&quot;&quot;" name="state"/>
1215@@ -364,7 +364,7 @@
1216 <field eval="&quot;&quot;&quot;Cancel&quot;&quot;&quot;" name="name"/>
1217 <field name="transition_id" ref="sale.process_transition_deliver0"/>
1218 </record>
1219-<!--
1220+
1221 <record id="sale.process_transition_action_createinvoice0" model="process.transition.action">
1222 <field eval="&quot;&quot;&quot;action_invoice_create&quot;&quot;&quot;" name="action"/>
1223 <field eval="&quot;&quot;&quot;object&quot;&quot;&quot;" name="state"/>
1224
1225=== modified file 'bin/addons/msf_profile/__openerp__.py'
1226--- bin/addons/msf_profile/__openerp__.py 2017-08-21 10:09:52 +0000
1227+++ bin/addons/msf_profile/__openerp__.py 2017-10-02 12:34:11 +0000
1228@@ -40,7 +40,7 @@
1229 "account_period_closing_level",
1230 # As register_accounting has account_override and purchase_override dependancies, no need to add them after that
1231 #"purchase_override",
1232- "sale_override",
1233+ "sale",
1234 "stock_override",
1235 "msf_order_date",
1236 "purchase_compare_rfq",
1237@@ -116,7 +116,6 @@
1238 "update_xml": [
1239 "security/ir.model.access.csv",
1240 "report.xml",
1241- "purchase_double_validation_workflow.xml",
1242 "usability.xml",
1243 "user_access_configurator_view.xml",
1244 "unifield_version_view.xml",
1245
1246=== modified file 'bin/addons/msf_profile/i18n/fr_MF.po'
1247--- bin/addons/msf_profile/i18n/fr_MF.po 2017-09-18 12:35:40 +0000
1248+++ bin/addons/msf_profile/i18n/fr_MF.po 2017-10-02 12:34:11 +0000
1249@@ -98454,7 +98454,7 @@
1250 "- 'Catégorie Bilan/Cte de Résultat' ne doit pas être vide."
1251
1252 #. module: msf_doc_import
1253-#: code:addons/msf_doc_import/wizard/wizard_import_invoice_line.py:168
1254+#: code:addons/msf_doc_import/wizard/wizard_import_invoice_line.py:184
1255 #, python-format
1256 msgid "\n"
1257 "- 'Account Type' should be in ('expense', 'income', 'receivables')."
1258
1259=== removed file 'bin/addons/msf_profile/purchase_double_validation_workflow.xml'
1260--- bin/addons/msf_profile/purchase_double_validation_workflow.xml 2012-06-27 15:03:40 +0000
1261+++ bin/addons/msf_profile/purchase_double_validation_workflow.xml 1970-01-01 00:00:00 +0000
1262@@ -1,18 +0,0 @@
1263-<?xml version="1.0" encoding="utf-8"?>
1264-<openerp>
1265- <data>
1266-
1267- <record id="purchase.trans_confirmed_router" model="workflow.transition">
1268- <field name="condition">amount_total &gt;= 0</field>
1269- <field name="signal">purchase_approve</field>
1270- <field name="group_id" ref="purchase.group_purchase_manager"/>
1271- </record>
1272-
1273- <record id="purchase_double_validation.trans_waiting_confirmed" model="workflow.transition">
1274- <field name="act_from" ref="purchase.act_confirmed"/>
1275- <field name="act_to" ref="purchase.act_router"/>
1276- <field name="condition">amount_total &lt; 0</field>
1277- </record>
1278-
1279- </data>
1280-</openerp>
1281
1282=== modified file 'bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_10-lines-removed_all-groups.xml'
1283--- bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_10-lines-removed_all-groups.xml 2016-11-14 11:27:07 +0000
1284+++ bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_10-lines-removed_all-groups.xml 2017-10-02 12:34:11 +0000
1285@@ -2158,7 +2158,7 @@
1286 <Cell><Data ss:Type="String">YES</Data></Cell>
1287 </Row>
1288 <Row ss:AutoFitHeight="0">
1289- <Cell><Data ss:Type="String">sale_override</Data></Cell>
1290+ <Cell><Data ss:Type="String">sale</Data></Cell>
1291 <Cell><Data ss:Type="String">menu_localisation</Data></Cell>
1292 <Cell><Data ss:Type="Number">0</Data></Cell>
1293 <Cell><Data ss:Type="String">Localisation</Data></Cell>
1294@@ -2173,7 +2173,7 @@
1295 <Cell><Data ss:Type="String">NO</Data></Cell>
1296 </Row>
1297 <Row ss:AutoFitHeight="0">
1298- <Cell><Data ss:Type="String">sale_override</Data></Cell>
1299+ <Cell><Data ss:Type="String">sale</Data></Cell>
1300 <Cell><Data ss:Type="String">menu_country_partner</Data></Cell>
1301 <Cell><Data ss:Type="String">++</Data></Cell>
1302 <Cell><Data ss:Type="String">Countries</Data></Cell>
1303@@ -2188,7 +2188,7 @@
1304 <Cell><Data ss:Type="String">NO</Data></Cell>
1305 </Row>
1306 <Row ss:AutoFitHeight="0">
1307- <Cell><Data ss:Type="String">sale_override</Data></Cell>
1308+ <Cell><Data ss:Type="String">sale</Data></Cell>
1309 <Cell><Data ss:Type="String">menu_country_state_partner</Data></Cell>
1310 <Cell><Data ss:Type="String">++</Data></Cell>
1311 <Cell><Data ss:Type="String">Fed. States</Data></Cell>
1312
1313=== modified file 'bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_10-lines-removed_no-cashier_no-productManager.xml'
1314--- bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_10-lines-removed_no-cashier_no-productManager.xml 2016-11-14 11:27:07 +0000
1315+++ bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_10-lines-removed_no-cashier_no-productManager.xml 2017-10-02 12:34:11 +0000
1316@@ -2002,7 +2002,7 @@
1317 <Cell><Data ss:Type="String">YES</Data></Cell>
1318 </Row>
1319 <Row ss:AutoFitHeight="0">
1320- <Cell><Data ss:Type="String">sale_override</Data></Cell>
1321+ <Cell><Data ss:Type="String">sale</Data></Cell>
1322 <Cell><Data ss:Type="String">menu_localisation</Data></Cell>
1323 <Cell><Data ss:Type="Number">0</Data></Cell>
1324 <Cell><Data ss:Type="String">Localisation</Data></Cell>
1325@@ -2016,7 +2016,7 @@
1326 <Cell><Data ss:Type="String">NO</Data></Cell>
1327 </Row>
1328 <Row ss:AutoFitHeight="0">
1329- <Cell><Data ss:Type="String">sale_override</Data></Cell>
1330+ <Cell><Data ss:Type="String">sale</Data></Cell>
1331 <Cell><Data ss:Type="String">menu_country_partner</Data></Cell>
1332 <Cell><Data ss:Type="String">++</Data></Cell>
1333 <Cell><Data ss:Type="String">Countries</Data></Cell>
1334@@ -2030,7 +2030,7 @@
1335 <Cell><Data ss:Type="String">NO</Data></Cell>
1336 </Row>
1337 <Row ss:AutoFitHeight="0">
1338- <Cell><Data ss:Type="String">sale_override</Data></Cell>
1339+ <Cell><Data ss:Type="String">sale</Data></Cell>
1340 <Cell><Data ss:Type="String">menu_country_state_partner</Data></Cell>
1341 <Cell><Data ss:Type="String">++</Data></Cell>
1342 <Cell><Data ss:Type="String">Fed. States</Data></Cell>
1343
1344=== modified file 'bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_all-lines_all-groups.xml'
1345--- bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_all-lines_all-groups.xml 2016-11-14 11:27:07 +0000
1346+++ bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_all-lines_all-groups.xml 2017-10-02 12:34:11 +0000
1347@@ -2297,7 +2297,7 @@
1348 <Cell><Data ss:Type="String">YES</Data></Cell>
1349 </Row>
1350 <Row>
1351- <Cell><Data ss:Type="String">sale_override</Data></Cell>
1352+ <Cell><Data ss:Type="String">sale</Data></Cell>
1353 <Cell><Data ss:Type="String">menu_localisation</Data></Cell>
1354 <Cell><Data ss:Type="Number">0</Data></Cell>
1355 <Cell><Data ss:Type="String">Localisation</Data></Cell>
1356@@ -2312,7 +2312,7 @@
1357 <Cell><Data ss:Type="String">NO</Data></Cell>
1358 </Row>
1359 <Row>
1360- <Cell><Data ss:Type="String">sale_override</Data></Cell>
1361+ <Cell><Data ss:Type="String">sale</Data></Cell>
1362 <Cell><Data ss:Type="String">menu_country_partner</Data></Cell>
1363 <Cell><Data ss:Type="String">++</Data></Cell>
1364 <Cell><Data ss:Type="String">Countries</Data></Cell>
1365@@ -2327,7 +2327,7 @@
1366 <Cell><Data ss:Type="String">NO</Data></Cell>
1367 </Row>
1368 <Row>
1369- <Cell><Data ss:Type="String">sale_override</Data></Cell>
1370+ <Cell><Data ss:Type="String">sale</Data></Cell>
1371 <Cell><Data ss:Type="String">menu_country_state_partner</Data></Cell>
1372 <Cell><Data ss:Type="String">++</Data></Cell>
1373 <Cell><Data ss:Type="String">Fed. States</Data></Cell>
1374
1375=== modified file 'bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_all-lines_all-groups_misformed_no-header.xml'
1376--- bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_all-lines_all-groups_misformed_no-header.xml 2016-11-14 11:27:07 +0000
1377+++ bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_all-lines_all-groups_misformed_no-header.xml 2017-10-02 12:34:11 +0000
1378@@ -2273,7 +2273,7 @@
1379 <Cell><Data ss:Type="String">YES</Data></Cell>
1380 </Row>
1381 <Row ss:AutoFitHeight="0">
1382- <Cell><Data ss:Type="String">sale_override</Data></Cell>
1383+ <Cell><Data ss:Type="String">sale</Data></Cell>
1384 <Cell><Data ss:Type="String">menu_localisation</Data></Cell>
1385 <Cell><Data ss:Type="Number">0</Data></Cell>
1386 <Cell><Data ss:Type="String">Localisation</Data></Cell>
1387@@ -2288,7 +2288,7 @@
1388 <Cell><Data ss:Type="String">NO</Data></Cell>
1389 </Row>
1390 <Row ss:AutoFitHeight="0">
1391- <Cell><Data ss:Type="String">sale_override</Data></Cell>
1392+ <Cell><Data ss:Type="String">sale</Data></Cell>
1393 <Cell><Data ss:Type="String">menu_country_partner</Data></Cell>
1394 <Cell><Data ss:Type="String">++</Data></Cell>
1395 <Cell><Data ss:Type="String">Countries</Data></Cell>
1396@@ -2303,7 +2303,7 @@
1397 <Cell><Data ss:Type="String">NO</Data></Cell>
1398 </Row>
1399 <Row ss:AutoFitHeight="0">
1400- <Cell><Data ss:Type="String">sale_override</Data></Cell>
1401+ <Cell><Data ss:Type="String">sale</Data></Cell>
1402 <Cell><Data ss:Type="String">menu_country_state_partner</Data></Cell>
1403 <Cell><Data ss:Type="String">++</Data></Cell>
1404 <Cell><Data ss:Type="String">Fed. States</Data></Cell>
1405
1406=== modified file 'bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_all-lines_no-cashier_no-productManager.xml'
1407--- bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_all-lines_no-cashier_no-productManager.xml 2016-11-14 11:27:07 +0000
1408+++ bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_all-lines_no-cashier_no-productManager.xml 2017-10-02 12:34:11 +0000
1409@@ -2131,7 +2131,7 @@
1410 <Cell><Data ss:Type="String">YES</Data></Cell>
1411 </Row>
1412 <Row ss:AutoFitHeight="0">
1413- <Cell><Data ss:Type="String">sale_override</Data></Cell>
1414+ <Cell><Data ss:Type="String">sale</Data></Cell>
1415 <Cell><Data ss:Type="String">menu_localisation</Data></Cell>
1416 <Cell><Data ss:Type="Number">0</Data></Cell>
1417 <Cell><Data ss:Type="String">Localisation</Data></Cell>
1418@@ -2145,7 +2145,7 @@
1419 <Cell><Data ss:Type="String">NO</Data></Cell>
1420 </Row>
1421 <Row ss:AutoFitHeight="0">
1422- <Cell><Data ss:Type="String">sale_override</Data></Cell>
1423+ <Cell><Data ss:Type="String">sale</Data></Cell>
1424 <Cell><Data ss:Type="String">menu_country_partner</Data></Cell>
1425 <Cell><Data ss:Type="String">++</Data></Cell>
1426 <Cell><Data ss:Type="String">Countries</Data></Cell>
1427@@ -2159,7 +2159,7 @@
1428 <Cell><Data ss:Type="String">NO</Data></Cell>
1429 </Row>
1430 <Row ss:AutoFitHeight="0">
1431- <Cell><Data ss:Type="String">sale_override</Data></Cell>
1432+ <Cell><Data ss:Type="String">sale</Data></Cell>
1433 <Cell><Data ss:Type="String">menu_country_state_partner</Data></Cell>
1434 <Cell><Data ss:Type="String">++</Data></Cell>
1435 <Cell><Data ss:Type="String">Fed. States</Data></Cell>
1436
1437=== modified file 'bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_all-lines_no-groups.xml'
1438--- bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_all-lines_no-groups.xml 2016-11-14 11:27:07 +0000
1439+++ bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_all-lines_no-groups.xml 2017-10-02 12:34:11 +0000
1440@@ -930,19 +930,19 @@
1441 <Cell><Data ss:Type="String">Reconfigure</Data></Cell>
1442 </Row>
1443 <Row ss:AutoFitHeight="0">
1444- <Cell><Data ss:Type="String">sale_override</Data></Cell>
1445+ <Cell><Data ss:Type="String">sale</Data></Cell>
1446 <Cell><Data ss:Type="String">menu_localisation</Data></Cell>
1447 <Cell><Data ss:Type="Number">0</Data></Cell>
1448 <Cell><Data ss:Type="String">Localisation</Data></Cell>
1449 </Row>
1450 <Row ss:AutoFitHeight="0">
1451- <Cell><Data ss:Type="String">sale_override</Data></Cell>
1452+ <Cell><Data ss:Type="String">sale</Data></Cell>
1453 <Cell><Data ss:Type="String">menu_country_partner</Data></Cell>
1454 <Cell><Data ss:Type="String">++</Data></Cell>
1455 <Cell><Data ss:Type="String">Countries</Data></Cell>
1456 </Row>
1457 <Row ss:AutoFitHeight="0">
1458- <Cell><Data ss:Type="String">sale_override</Data></Cell>
1459+ <Cell><Data ss:Type="String">sale</Data></Cell>
1460 <Cell><Data ss:Type="String">menu_country_state_partner</Data></Cell>
1461 <Cell><Data ss:Type="String">++</Data></Cell>
1462 <Cell><Data ss:Type="String">Fed. States</Data></Cell>
1463
1464=== modified file 'bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_original.csv'
1465--- bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_original.csv 2013-03-07 16:41:45 +0000
1466+++ bin/addons/msf_profile/test/user_rights_test_files/user_rights_01_original.csv 2017-10-02 12:34:11 +0000
1467@@ -148,9 +148,9 @@
1468 base;menu_ir_property_form_all;+++;Configuration Parameters;NO;NO;NO;NO;NO;NO;NO;NO;NO;YES;YES;NO;NO;NO;NO;NO;NO;NO
1469 msf_instance;menu_action_msf_instance_tree;++;Proprietary Instances;NO;NO;NO;NO;NO;NO;NO;NO;NO;YES;YES;NO;NO;NO;NO;NO;NO;NO
1470 base;menu_view_base_module_configuration;++;Reconfigure;NO;NO;NO;NO;NO;NO;NO;NO;NO;YES;YES;NO;NO;NO;NO;NO;NO;YES
1471-sale_override;menu_localisation;0;Localisation;;;;;;;;;;YES;YES;NO;NO;NO;NO;NO;NO;NO
1472-sale_override;menu_country_partner;++;Countries;;;;;;;;;;YES;YES;NO;NO;NO;NO;NO;NO;NO
1473-sale_override;menu_country_state_partner;++;Fed. States;;;;;;;;;;YES;YES;NO;NO;NO;NO;NO;NO;NO
1474+sale;menu_localisation;0;Localisation;;;;;;;;;;YES;YES;NO;NO;NO;NO;NO;NO;NO
1475+sale;menu_country_partner;++;Countries;;;;;;;;;;YES;YES;NO;NO;NO;NO;NO;NO;NO
1476+sale;menu_country_state_partner;++;Fed. States;;;;;;;;;;YES;YES;NO;NO;NO;NO;NO;NO;NO
1477 base;menu_custom;0;Customization;NO;NO;NO;NO;NO;NO;NO;NO;NO;YES;YES;NO;NO;NO;NO;NO;NO;NO
1478 base;next_id_2;++;User Interface;NO;NO;NO;NO;NO;NO;NO;NO;NO;YES;YES;NO;NO;NO;NO;NO;NO;NO
1479 base;menu_grant_menu_access;+++;Menu Items;NO;NO;NO;NO;NO;NO;NO;NO;NO;YES;YES;NO;NO;NO;NO;NO;NO;NO
1480
1481=== modified file 'bin/addons/msf_sync_data_server/data/sync_server.message_rule.csv'
1482--- bin/addons/msf_sync_data_server/data/sync_server.message_rule.csv 2017-08-03 15:16:40 +0000
1483+++ bin/addons/msf_sync_data_server/data/sync_server.message_rule.csv 2017-10-02 12:34:11 +0000
1484@@ -1,18 +1,15 @@
1485 id,active,applies_to_type,arguments,domain,destination_name,type_id,remote_call,model_id,name,sequence_number,status
1486 msf_sync_data_server.resourcing_lines,TRUE,TRUE,"['sync_order_line_db_id', 'partner_id', 'resource_ok', 'resource_sync_line_db_id', 'partner_type']","[('partner_type','!=','external'), ('partner_id', '!=', False)]",partner_id,MISSION,sale.order.line.cancel.create_line,sale.order.line.cancel,Resourcing lines,1,Valid
1487-msf_sync_data_server.po_creates_fo,TRUE,TRUE,"['name', 'analytic_distribution_id/id', 'delivery_requested_date','details','notes', 'origin', 'categ', 'order_type', 'priority', 'loan_duration', 'is_a_counterpart', 'sourced_references', 'stock_take_date', 'order_line/product_id/id', 'order_line/product_id/name','order_line/id', 'order_line/name', 'order_line/product_qty', 'order_line/product_uom', 'order_line/price_unit', 'order_line/analytic_distribution_id/id','order_line/comment','order_line/have_analytic_distribution_from_header','order_line/line_number', 'order_line/nomen_manda_0/id','order_line/nomen_manda_1/id','order_line/nomen_manda_2/id','order_line/nomen_manda_3/id', 'order_line/sync_order_line_db_id', 'order_line/nomenclature_description','order_line/notes','order_line/default_name','order_line/default_code','order_line/is_line_split','order_line/date_planned', 'order_line/stock_take_date']","['&','&','&',('partner_type','!=','external'),('state','in',['confirmed']), ('split_po','!=','True'),('push_fo','!=','True')]",partner_id,MISSION,sale.order.create_so,purchase.order,PO creates FO,2,Valid
1488-msf_sync_data_server.split_po_lines_from_splitted_fo_lines,TRUE,TRUE,"['partner_id','old_sync_order_line_db_id','new_sync_order_line_db_id','new_line_qty','old_line_qty']","[]",partner_id,MISSION,purchase.order.line.to.split.create_from_sync_message,sync.sale.order.line.split,Split PO lines from splitted FO lines,3,Valid
1489-msf_sync_data_server.validated_fo_updates_po,TRUE,TRUE,"['name','state','analytic_distribution_id/id', 'original_so_id_sale_order','categ', 'order_type', 'priority', 'loan_duration','delivery_confirmed_date','est_transport_lead_time', 'transport_type', 'ready_to_ship_date', 'details', 'note','client_order_ref', 'stock_take_date', 'order_line/product_id/id', 'order_line/product_id/name','order_line/id', 'order_line/name', 'order_line/product_uom_qty', 'order_line/product_uom', 'order_line/price_unit', 'order_line/analytic_distribution_id/id','order_line/comment','order_line/have_analytic_distribution_from_header','order_line/line_number', 'order_line/nomen_manda_0/id','order_line/nomen_manda_1/id','order_line/nomen_manda_2/id','order_line/nomen_manda_3/id', 'order_line/sync_order_line_db_id', 'order_line/nomenclature_description','order_line/notes','order_line/default_name','order_line/default_code','order_line/date_planned','order_line/is_line_split', 'order_line/confirmed_delivery_date', 'order_line/cancel_split_ok', 'order_line/stock_take_date']","['&','&','&',('partner_type','!=','external'),('state','in',['validated', 'done']),('original_so_id_sale_order','=',''), ('client_order_ref','!=',False)]",partner_id,MISSION,purchase.order.validated_fo_update_original_po,sale.order,Validated FO updates PO,4,Valid
1490-msf_sync_data_server.normal_fo_creates_po,TRUE,TRUE,"['name','state','analytic_distribution_id/id','parent_order_name','delivery_confirmed_date','est_transport_lead_time', 'categ', 'order_type', 'priority', 'loan_duration','transport_type', 'ready_to_ship_date', 'details', 'note','client_order_ref', 'stock_take_date', 'order_line/product_id/id', 'order_line/product_id/name','order_line/id', 'order_line/name', 'order_line/product_uom_qty', 'order_line/product_uom', 'order_line/price_unit', 'order_line/analytic_distribution_id/id','order_line/comment','order_line/have_analytic_distribution_from_header','order_line/line_number', 'order_line/nomen_manda_0/id','order_line/nomen_manda_1/id','order_line/nomen_manda_2/id','order_line/nomen_manda_3/id', 'order_line/sync_order_line_db_id', 'order_line/nomenclature_description','order_line/notes','order_line/default_name','order_line/default_code','order_line/date_planned', 'order_line/confirmed_delivery_date', 'order_line/stock_take_date']","['&','&','&',('partner_type','!=','external'),('state','in',['validated','done']),('client_order_ref','=',False),('split_type_sale_order','=','original_sale_order')]",partner_id,MISSION,purchase.order.normal_fo_create_po,sale.order,Normal FO creates PO,5,Valid
1491-msf_sync_data_server.split_fo_creates_split_po,TRUE,TRUE,"['name','state','analytic_distribution_id/id', 'delivery_confirmed_date','categ', 'order_type', 'priority', 'loan_duration','est_transport_lead_time', 'transport_type', 'ready_to_ship_date', 'details', 'note','client_order_ref', 'stock_take_date', 'order_line/product_id/id', 'order_line/product_id/name','order_line/id', 'order_line/is_line_split','order_line/name', 'order_line/product_uom_qty', 'order_line/product_uom', 'order_line/price_unit', 'order_line/analytic_distribution_id/id','order_line/comment','order_line/have_analytic_distribution_from_header','order_line/line_number', 'order_line/nomen_manda_0/id','order_line/nomen_manda_1/id','order_line/nomen_manda_2/id','order_line/nomen_manda_3/id', 'order_line/sync_order_line_db_id', 'order_line/nomenclature_description','order_line/notes','order_line/default_name','order_line/default_code','order_line/date_planned', 'order_line/confirmed_delivery_date', 'order_line/source_sync_line_id', 'order_line/stock_take_date']","['&',('partner_type','!=','external'),('original_so_id_sale_order','!=','False')]",partner_id,MISSION,purchase.order.create_split_po,sale.order,Split FO creates Split PO,6,Valid
1492-msf_sync_data_server.split_fo_updates_split_po,TRUE,TRUE,"['name', 'state', 'client_order_ref','sync_date','analytic_distribution_id/id','categ', 'order_type', 'priority', 'loan_duration', 'delivery_confirmed_date','est_transport_lead_time', 'transport_type', 'note','original_so_id_sale_order', 'ready_to_ship_date', 'stock_take_date', 'order_line/product_id/id', 'order_line/product_id/name','order_line/id', 'order_line/name', 'order_line/is_line_split','order_line/product_uom_qty', 'order_line/product_uom', 'order_line/price_unit', 'order_line/analytic_distribution_id/id','order_line/comment','order_line/have_analytic_distribution_from_header','order_line/line_number', 'order_line/nomen_manda_0/id','order_line/nomen_manda_1/id','order_line/nomen_manda_2/id','order_line/nomen_manda_3/id', 'order_line/sync_order_line_db_id', 'order_line/nomenclature_description','order_line/notes','order_line/default_name','order_line/default_code', 'order_line/confirmed_delivery_date', 'order_line/source_sync_line_id', 'order_line/sync_sourced_origin', 'order_line/stock_take_date']","['&','&',('partner_type','!=','external'),('state','in',['manual','progress','shipping_except','invoice_except','done']),('original_so_id_sale_order','!=','False')]",partner_id,MISSION,purchase.order.update_split_po,sale.order,Split FO updates split PO,7,Valid
1493+msf_sync_data_server.po_creates_fo,TRUE,TRUE,"['name', 'analytic_distribution_id/id', 'delivery_requested_date','details','notes', 'origin', 'categ', 'order_type', 'priority', 'loan_duration', 'is_a_counterpart', 'sourced_references', 'stock_take_date']","['&','&','&',('partner_type','not in',['external','esc']),('state','in',['validated']), ('split_po','!=','True'),('push_fo','!=','True')]",partner_id,MISSION,sale.order.create_so,purchase.order,PO creates FO,2,Valid
1494+msf_sync_data_server.normal_fo_creates_po,TRUE,TRUE,"['name','state','analytic_distribution_id/id','parent_order_name','delivery_confirmed_date','est_transport_lead_time', 'categ', 'order_type', 'priority', 'loan_duration','transport_type', 'ready_to_ship_date', 'details', 'note','client_order_ref', 'stock_take_date', 'order_line/product_id/id', 'order_line/product_id/name','order_line/id', 'order_line/name', 'order_line/product_uom_qty', 'order_line/product_uom', 'order_line/price_unit', 'order_line/analytic_distribution_id/id','order_line/comment','order_line/have_analytic_distribution_from_header','order_line/line_number', 'order_line/nomen_manda_0/id','order_line/nomen_manda_1/id','order_line/nomen_manda_2/id','order_line/nomen_manda_3/id', 'order_line/sync_order_line_db_id', 'order_line/nomenclature_description','order_line/notes','order_line/default_name','order_line/default_code','order_line/date_planned', 'order_line/confirmed_delivery_date', 'order_line/stock_take_date']","[('partner_type','!=','external'),('state','not in',['draft']),('client_order_ref','=',False)]",partner_id,MISSION,purchase.order.normal_fo_create_po,sale.order,Normal FO creates PO,5,Valid
1495 msf_sync_data_server.po_updates_so_ref,TRUE,TRUE,"['name','state','partner_ref']","['&','&','&',('partner_type','!=','external'),('state','in',['confirmed','sourced','split', 'approved']),('partner_ref','!=',False),'!',('partner_ref', 'like', 'invalid_by_recovery')]",partner_id,MISSION,sale.order.update_sub_so_ref,purchase.order,PO updates SO ref,8,Valid
1496 msf_sync_data_server.fo_updates_po_ref,TRUE,TRUE,"['name','state','client_order_ref']","['&','&','&',('partner_type','!=','external'),('client_order_ref','!=',False),('split_type_sale_order','=','original_sale_order'),'!',('client_order_ref', 'like', 'invalid_by_recovery')]",partner_id,MISSION,purchase.order.update_fo_ref,sale.order,FO updates PO ref,9,Valid
1497 msf_sync_data_server.update_in_ref,TRUE,TRUE,"['name','shipment_ref']","['&',('shipment_ref','!=',False),'!',('shipment_ref', 'like', 'invalid_by_recovery')]",partner_id,MISSION,stock.picking.update_in_ref,stock.picking,IN updates ref to OUT SHIP,10,Valid
1498-msf_sync_data_server.canceled_fo_cancels_po,TRUE,TRUE,"['name','state', 'client_order_ref']","[('state', '=', 'cancel'), ('client_order_ref', '!=', '')]",partner_id,MISSION,purchase.order.canceled_fo_cancel_po,sale.order,Canceled FO cancels PO,18,Valid
1499+msf_sync_data_server.pol_create_sol,TRUE,TRUE,"['sync_local_id', 'order_id/name','product_id/id', 'product_id/name', 'name', 'state', 'product_qty', 'product_uom', 'price_unit', 'analytic_distribution_id/id','comment','have_analytic_distribution_from_header','line_number', 'nomen_manda_0/id','nomen_manda_1/id','nomen_manda_2/id','nomen_manda_3/id', 'nomenclature_description','notes','default_name','default_code','is_line_split','date_planned', 'stock_take_date']","[('sync_linked_sol', '=', False), ('order_id.partner_type', 'not in',['external','esc']), ('state', 'in', ['validated', 'confirmed', 'done']), ('order_id.state', 'not in', ['draft', 'cancel'])]",partner_id,MISSION,sale.order.line.create_so_line,purchase.order.line,PO line creates FO line,11,Valid
1500+msf_sync_data_server.sol_updates_pol,TRUE,TRUE,"['resourced_original_line/id', 'resourced_original_remote_line','sync_sourced_origin', 'sync_local_id', 'sync_linked_pol', 'order_id/name', 'product_id/id', 'product_id/name', 'name', 'state','product_uom_qty', 'product_uom', 'price_unit', 'analytic_distribution_id/id','comment','have_analytic_distribution_from_header','line_number', 'nomen_manda_0/id','nomen_manda_1/id','nomen_manda_2/id','nomen_manda_3/id', 'nomenclature_description','notes','default_name','default_code','date_planned','is_line_split', 'original_line_id/id', 'confirmed_delivery_date', 'stock_take_date', 'cancel_split_ok']","[('order_id.partner_type', '!=', 'external'), ('state', '!=', 'draft')]",partner_id,MISSION,purchase.order.line.sol_update_original_pol,sale.order.line,FO line updates PO line,12,Valid
1501 msf_sync_data_server.partial_shipped_coordo_updates_in_at_project,TRUE,TRUE,"['name', 'state', 'origin', 'partner_type_stock_picking', 'shipment_id/name', 'min_date', 'note', 'move_lines/processed_stock_move', 'move_lines/id', 'move_lines/state','move_lines/original_qty_partial', 'move_lines/line_number', 'move_lines/name', 'move_lines/change_reason', 'move_lines/product_id/id', 'move_lines/product_id/name', 'move_lines/product_qty', 'move_lines/prodlot_id/id','move_lines/prodlot_id/name','move_lines/prodlot_id/life_date', 'move_lines/expired_date', 'move_lines/asset_id/id','move_lines/product_uom/id', 'move_lines/product_uom/name', 'move_lines/date', 'move_lines/date_expected', 'move_lines/note', 'move_lines/location_dest_id/usage', 'move_lines/comment']","['&','&','&','&','&','&',('partner_type_stock_picking', '!=', 'external'), ('type', '=', 'out'), ('subtype', 'in', ['standard', 'packing']), ('state', '=', 'done'), ('already_shipped', '=', True), ('do_not_sync', '=', False), ('claim', '=', False)]",partner_id,MISSION,stock.picking.partial_shipped_fo_updates_in_po,stock.picking,Partial shipped at Coordo updates IN at Project,19,Valid
1502 msf_sync_data_server.moves_from_dpo_closed_coordo_updates_in_at_project,TRUE,TRUE,"['name', 'state', 'origin', 'subtype', 'partner_type_stock_picking', 'shipment_id/name', 'min_date', 'note', 'move_lines/processed_stock_move', 'move_lines/id', 'move_lines/state','move_lines/original_qty_partial', 'move_lines/line_number', 'move_lines/name', 'move_lines/change_reason', 'move_lines/product_id/id', 'move_lines/product_id/name', 'move_lines/product_qty', 'move_lines/prodlot_id/id','move_lines/prodlot_id/name','move_lines/prodlot_id/life_date', 'move_lines/expired_date', 'move_lines/asset_id/id','move_lines/product_uom/id', 'move_lines/product_uom/name', 'move_lines/date', 'move_lines/date_expected', 'move_lines/note', 'move_lines/dpo_line_id', 'move_lines/comment']","['&', '&', '&', ('partner_type_stock_picking', '!=', 'external'), ('type', '=', 'out'), ('subtype', 'in', ['picking', 'standard']), ('dpo_out', '=', True)]",partner_id,MISSION,stock.picking.partial_shippped_dpo_updates_in_po,stock.picking,Moves from DPO closed at Coordo updates IN at Project,20,Valid
1503-msf_sync_data_server.dpo_service_lines_update_in_at_project,FALSE,TRUE,"['order_id/name', 'order_id/delivery_confirmed_date', 'fake_id', 'origin', 'confirmed_delivery_date', 'name', 'product_uom/id', 'product_uom/name', 'link_sol_id/line_number', 'notes', 'product_qty', 'product_id/name', 'product_id/id', 'comment']","[('dest_partner_id.partner_type', '=', 'internal'), ('order_id.order_type', '=', 'direct'), ('order_id.state', 'in', ['approved', 'done']), ('product_id.type', 'in', ['service', 'service_recep'])]",dest_partner_id,MISSION,purchase.order.line.confirmed_dpo_service_lines_update_in_po,purchase.order.line,DPO service lines update IN at Project,21,Valid
1504+msf_sync_data_server.dpo_service_lines_update_in_at_project,FALSE,TRUE,"['order_id/name', 'order_id/delivery_confirmed_date', 'sync_local_id', 'origin', 'confirmed_delivery_date', 'name', 'product_uom/id', 'product_uom/name', 'link_sol_id/line_number', 'notes', 'product_qty', 'product_id/name', 'product_id/id', 'comment']","[('dest_partner_id.partner_type', '=', 'internal'), ('order_id.order_type', '=', 'direct'), ('order_id.state', 'in', ['approved', 'done']), ('product_id.type', 'in', ['service', 'service_recep'])]",dest_partner_id,MISSION,purchase.order.line.confirmed_dpo_service_lines_update_in_po,purchase.order.line,DPO service lines update IN at Project,21,Valid
1505 msf_sync_data_server.cancel_out_at_coordo_cancels_in_at_project,TRUE,TRUE,"['name', 'state', 'origin']","['&','&','&','&',('partner_type_stock_picking', '!=', 'external'), ('type', '=', 'out'),('state', '=', 'cancel'),('subtype', '=', 'standard'),('do_not_sync', '=', False)]",partner_id,MISSION,stock.picking.cancel_out_pick_cancel_in,stock.picking,Canceled OUT at Coordo cancels IN at Project,22,Valid
1506 msf_sync_data_server.cancel_pick_at_coordo_cancels_in_at_project,TRUE,TRUE,"['name', 'state', 'origin']","['&','&','&','&','&',('partner_type_stock_picking', '!=', 'external'), ('type', '=', 'out'),('state', '=', 'cancel'),('subtype', '=', 'picking'),('backorder_id', '=', False),('do_not_sync', '=', False)]",partner_id,MISSION,stock.picking.cancel_out_pick_cancel_in,stock.picking,Canceled PICK at Coordo cancels IN at Project,23,Valid
1507 msf_sync_data_server.cancel_stock_move_at_coordo_cancels_in_at_project,TRUE,TRUE,"['name', 'state', 'origin', 'date_cancel']","['&','&','&','&','&','&',('picking_id.partner_type_stock_picking', '!=', 'external'), ('type', '=', 'out'),('state', '=', 'cancel'),('picking_id.state', '=', 'done'),('picking_id.do_not_sync', '=', False),('to_be_sent', '=', True), '&', '|', ('picking_id.subtype', '=', 'picking'), ('picking_id.subtype', '=', 'standard'), ('picking_id.already_shipped', '=', False)]",partner_id,MISSION,stock.picking.cancel_stock_move_of_pick_cancel_in,stock.move,Canceled stock move cancels IN,24,Valid
1508
1509=== modified file 'bin/addons/order_types/__openerp__.py'
1510--- bin/addons/order_types/__openerp__.py 2012-06-13 15:32:42 +0000
1511+++ bin/addons/order_types/__openerp__.py 2017-10-02 12:34:11 +0000
1512@@ -30,7 +30,6 @@
1513 "stock",
1514 "product_expiry",
1515 "purchase_double_validation",
1516- "sale_override",
1517 "purchase_override"],
1518 "author": "TeMPO Consulting, MSF",
1519 "website": "",
1520
1521=== modified file 'bin/addons/procurement_request/__openerp__.py'
1522--- bin/addons/procurement_request/__openerp__.py 2013-09-16 06:13:17 +0000
1523+++ bin/addons/procurement_request/__openerp__.py 2017-10-02 12:34:11 +0000
1524@@ -2,7 +2,7 @@
1525 ##############################################################################
1526 #
1527 # OpenERP, Open Source Management Solution
1528-# Copyright (C) 2011 TeMPO Consulting, MSF
1529+# Copyright (C) 2011 TeMPO Consulting, MSF
1530 #
1531 # This program is free software: you can redistribute it and/or modify
1532 # it under the terms of the GNU Affero General Public License as
1533@@ -22,16 +22,15 @@
1534 {
1535 "name": "Internal Request",
1536 "version": "1.0",
1537- "depends": ["base",
1538- "sale",
1539- "sale_override",
1540- "stock_override",
1541- "msf_order_date",
1542+ "depends": ["base",
1543+ "sale",
1544+ "stock_override",
1545+ "msf_order_date",
1546 "stock",
1547 "threshold_value",
1548 "procurement_cycle",
1549 "procurement_auto",
1550- ],
1551+ ],
1552 "author": "TeMPO Consulting, MSF",
1553 "website": "",
1554 "category": "Sales & Purchases",
1555@@ -44,7 +43,6 @@
1556 'update_xml': [
1557 'procurement_request_view.xml',
1558 'procurement_request_sequence.xml',
1559- 'procurement_request_workflow.xml',
1560 'procurement_request_wizard.xml',
1561 'procurement_request_report.xml',
1562 'wizard/wizard_import_list_view.xml',
1563@@ -59,7 +57,7 @@
1564 ],
1565 'installable': True,
1566 'active': False,
1567-# 'certificate': 'certificate',
1568+ # 'certificate': 'certificate',
1569 }
1570
1571 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1572
1573=== modified file 'bin/addons/procurement_request/procurement_request.py'
1574--- bin/addons/procurement_request/procurement_request.py 2017-08-25 15:37:51 +0000
1575+++ bin/addons/procurement_request/procurement_request.py 2017-10-02 12:34:11 +0000
1576@@ -25,7 +25,6 @@
1577 from tools.translate import _
1578 import decimal_precision as dp
1579
1580-from sale_override import SALE_ORDER_STATE_SELECTION
1581 from msf_order_date.order_dates import compute_rts
1582
1583
1584@@ -353,7 +352,6 @@
1585 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty', 'type'], 10),
1586 },
1587 multi='by_type', help="The amount of lines sourced from stock"),
1588- 'state': fields.selection(SALE_ORDER_STATE_SELECTION, 'Order State', readonly=True, help="Gives the state of the quotation or sales order. \nThe exception state is automatically set when a cancel operation occurs in the invoice validation (Invoice Exception) or in the picking list process (Shipping Exception). \nThe 'Waiting Schedule' state is set when the invoice is confirmed but waiting for the scheduler to run on the date 'Ordered Date'.", select=True),
1589 'name': fields.char('Order Reference', size=64, required=True, readonly=True, select=True),
1590 'is_ir_from_po_cancel': fields.boolean('Is IR from a PO cancelled', invisible=True), # UFTP-82: flagging we are in an IR and its PO is cancelled
1591 }
1592@@ -568,59 +566,6 @@
1593
1594 return True
1595
1596- def confirm_procurement(self, cr, uid, ids, context=None):
1597- '''
1598- Confirmed the request
1599- '''
1600- if context is None:
1601- context = {}
1602-
1603- self.write(cr, uid, ids, {'state': 'progress'}, context=context)
1604-
1605- for request in self.browse(cr, uid, ids, context=context):
1606- if len(request.order_line) <= 0:
1607- raise osv.except_osv(_('Error'), _('You cannot confirm an Internal request with no lines !'))
1608- for line in request.order_line:
1609- # for FO
1610- if line.type == 'make_to_order' and not line.po_cft == 'cft':
1611- if not line.supplier:
1612- line_number = line.line_number
1613- request_name = request.name
1614- raise osv.except_osv(_('Error'), _('Please correct the line %s of the %s: the supplier is required for the procurement method "On Order" !') % (line_number, request_name))
1615- # an Internal Request without product can only have Internal, Intersection or Intermission partners.
1616- elif line.supplier and not line.product_id and line.order_id.procurement_request and line.supplier.partner_type not in ['internal', 'section', 'intermission', 'esc']:
1617- raise osv.except_osv(_('Warning'), _("""For an Internal Request with a procurement method 'On Order' and without product,
1618- the supplier must be either in 'Internal', 'Inter-Section', 'Intermission' or 'ESC' type.
1619- """))
1620- message = _("The internal request '%s' has been confirmed (nb lines: %s).") % (request.name, len(request.order_line))
1621- proc_view = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'procurement_request', 'procurement_request_form_view')
1622- context.update({'view_id': proc_view and proc_view[1] or False})
1623- self.log(cr, uid, request.id, message, context=context)
1624-
1625- self.action_ship_create(cr, uid, ids, context=context)
1626-
1627- return True
1628-
1629- def test_state_done(self, cr, uid, ids, mode, *args):
1630- if not self.test_state(cr, uid, ids, mode, *args):
1631- return False
1632-
1633- for ir in self.browse(cr, uid, ids):
1634- is_out = ir.location_requestor_id.usage == 'customer'
1635- if not is_out:
1636- return True
1637-
1638- ir_lines = [x.id for x in ir.order_line]
1639- out_move_ids = self.pool.get('stock.move').search(cr, uid, [
1640- ('picking_id.type', '=', 'out'),
1641- ('sale_line_id', 'in', ir_lines),
1642- ('state', 'not in', ['done', 'cancel']),
1643- ])
1644- if out_move_ids:
1645- return False
1646-
1647- return True
1648-
1649 def procurement_done(self, cr, uid, ids, context=None):
1650 '''
1651 Creates all procurement orders according to lines
1652@@ -675,27 +620,6 @@
1653 _name = 'sale.order.line'
1654 _inherit = 'sale.order.line'
1655
1656- def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
1657- '''
1658- Override the method to return 0.0 if the line is a procurement request line
1659- '''
1660- res = {}
1661- new_ids = []
1662- cur_obj = self.pool.get('res.currency')
1663- curr_browse = self.pool.get('res.users').browse(cr, uid, [uid], context)[0].company_id.currency_id
1664- for line in self.browse(cr, uid, ids):
1665- if line.order_id.procurement_request:
1666- subtotal = line.cost_price * line.product_uom_qty
1667- res[line.id] = cur_obj.round(cr, uid, curr_browse.rounding, subtotal)
1668- if line.cost_price > 0 and res[line.id] < 0.01:
1669- res[line.id] = 0.01
1670- else:
1671- new_ids.append(line.id)
1672-
1673- res.update(super(procurement_request_line, self)._amount_line(cr, uid, new_ids, field_name, arg, context=context))
1674-
1675- return res
1676-
1677 def create(self, cr, uid, vals, context=None):
1678 '''
1679 Adds the date_planned value.
1680@@ -764,7 +688,6 @@
1681 'cost_price': fields.float(string='Cost price', digits_compute=dp.get_precision('Sale Price Computation')),
1682 'procurement_request': fields.boolean(string='Internal Request', readonly=True),
1683 'latest': fields.char(size=64, string='Latest documents', readonly=True),
1684- 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', digits_compute=dp.get_precision('Sale Price')),
1685 'my_company_id': fields.many2one('res.company', 'Company', select=1),
1686 'supplier': fields.many2one('res.partner', 'Supplier', domain="[('id', '!=', my_company_id)]"),
1687 # openerp bug: eval invisible in p.o use the po line state and not the po state !
1688@@ -831,8 +754,14 @@
1689 res, test = product_obj._on_change_restriction_error(cr, uid, product_id, field_name='product_id', values={'value': vals['value']}, vals={'constraints': 'consumption'}, context=context)
1690 if test:
1691 return res
1692- vals['value'] = {'product_uom': product.uom_id.id, 'name': '[%s] %s' % (product.default_code, product.name),
1693- 'type': product.procure_method, 'comment_ok': True, 'cost_price': product.standard_price, }
1694+ vals['value'] = {
1695+ 'product_uom': product.uom_id.id,
1696+ 'name': '[%s] %s' % (product.default_code, product.name),
1697+ 'type': product.procure_method,
1698+ 'comment_ok': True,
1699+ 'cost_price': product.standard_price,
1700+ 'price_unit': product.list_price,
1701+ }
1702 if vals['value']['type'] != 'make_to_stock':
1703 vals['value'].update({'supplier': product.seller_ids and product.seller_ids[0].name.id})
1704 uom_val = uom_obj.read(cr, uid, [product.uom_id.id], ['category_id'])
1705@@ -892,42 +821,5 @@
1706
1707 procurement_request_line()
1708
1709-class purchase_order(osv.osv):
1710- _name = 'purchase.order'
1711- _inherit = 'purchase.order'
1712-
1713- def _hook_action_picking_create_modify_out_source_loc_check(self, cr, uid, ids, context=None, *args, **kwargs):
1714- '''
1715- Please copy this to your module's method also.
1716- This hook belongs to the action_picking_create method from purchase>purchase.py>purchase_order class
1717-
1718- - allow to choose whether or not the source location of the corresponding outgoing stock move should
1719- match the destination location of incoming stock move
1720- '''
1721- order_line = kwargs['order_line']
1722- move_id = kwargs['move_id']
1723- proc_obj = self.pool.get('procurement.order')
1724- move_obj = self.pool.get('stock.move')
1725- sale_line_obj = self.pool.get('sale.order.line')
1726- po_line_obj = self.pool.get('purchase.order.line')
1727- # If the line comes from an ISR and it's not splitted line,
1728- # change the move_dest_id of this line (and their children)
1729- # to match with the procurement ordre move destination
1730- if order_line.move_dest_id and not order_line.is_line_split: # UTP-972: Use the boolean for split line
1731- proc_ids = proc_obj.search(cr, uid, [('move_id', '=', order_line.move_dest_id.id)], context=context)
1732- so_line_ids = sale_line_obj.search(cr, uid, [('procurement_id', 'in', proc_ids)], context=context)
1733- po_line_ids = po_line_obj.search(cr, uid, [('move_dest_id', '=', order_line.move_dest_id.id)], context=context)
1734- if so_line_ids and all(not line.order_id or (line.order_id.procurement_request and line.order_id.location_requestor_id.usage != 'customer') for line in sale_line_obj.browse(cr, uid, so_line_ids, context=context)):
1735- for proc in proc_obj.browse(cr, uid, proc_ids, context=context):
1736- if proc.move_id:
1737- move_obj.write(cr, uid, [proc.move_id.id], {'state': 'draft'}, context=context)
1738- move_obj.unlink(cr, uid, [proc.move_id.id], context=context)
1739- proc_obj.write(cr, uid, [proc.id], {'move_id': move_id}, context=context)
1740- # Update the move_dest_id of all children to avoid the system to deal with a deleted stock move
1741- po_line_obj.write(cr, uid, po_line_ids, {'move_dest_id': move_id}, context=context)
1742-
1743- return super(purchase_order, self)._hook_action_picking_create_modify_out_source_loc_check(cr, uid, ids, context, *args, **kwargs)
1744-
1745-purchase_order()
1746
1747 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1748
1749=== modified file 'bin/addons/procurement_request/procurement_request_view.xml'
1750--- bin/addons/procurement_request/procurement_request_view.xml 2017-09-06 10:03:46 +0000
1751+++ bin/addons/procurement_request/procurement_request_view.xml 2017-10-02 12:34:11 +0000
1752@@ -38,7 +38,7 @@
1753 <field name="priority" eval="250" />
1754 <field name="arch" type="xml">
1755 <form string="Internal Request">
1756- <group colspan="6" attrs="{'invisible': [('state', '!=', 'validated')]}">
1757+ <group colspan="6" attrs="{'invisible': [('state', 'not in', ['validated', 'validated_p'])]}">
1758 <html>
1759 <p style="text-align:center; color: red; font-weight: bold; font-size: 1.2em;">
1760 WARNING: This document has already been validated, are you sure you wish to modify after validation?
1761@@ -47,12 +47,12 @@
1762 </group>
1763 <group col="6" colspan="4" name="header">
1764 <field name="name" />
1765- <field name="date_order" attrs="{'readonly': [('state', 'not in', ['draft', 'validated'])]}" />
1766- <field name="warehouse_id" widget="selection" attrs="{'readonly': [('state', 'not in', ['draft', 'validated'])]}" />
1767- <field name="requestor" attrs="{'readonly': [('state', 'not in', ['draft', 'validated'])]}" />
1768- <field name="location_requestor_id" required="1" attrs="{'readonly': [('state', 'not in', ['draft', 'validated'])]}" />
1769- <field name="delivery_requested_date" required="1" string="Requested date" attrs="{'readonly': [('state', 'not in', ['draft', 'validated'])]}" />
1770- <field name="origin" attrs="{'readonly': [('state', 'not in', ['draft', 'validated'])]}" />
1771+ <field name="date_order" attrs="{'readonly': [('state', 'not in', ['draft', 'draft_p', 'validated', 'validated_p'])]}" />
1772+ <field name="warehouse_id" widget="selection" attrs="{'readonly': [('state', 'not in', ['draft', 'draft_p', 'validated', 'validated_p'])]}" />
1773+ <field name="requestor" attrs="{'readonly': [('state', 'not in', ['draft', 'draft_p', 'validated', 'validated_p'])]}" />
1774+ <field name="location_requestor_id" required="1" attrs="{'readonly': [('state', 'not in', ['draft', 'draft_p', 'validated', 'validated_p'])]}" />
1775+ <field name="delivery_requested_date" required="1" string="Requested date" attrs="{'readonly': [('state', 'not in', ['draft', 'draft_p', 'validated', 'validated_p'])]}" />
1776+ <field name="origin" attrs="{'readonly': [('state', 'not in', ['draft', 'draft_p', 'validated', 'validated_p'])]}" />
1777 <field name="functional_currency_id" />
1778 <field name="categ" on_change="onchange_categ(categ)" />
1779 <group colspan="2" col="3">
1780@@ -66,7 +66,7 @@
1781 <notebook colspan="5">
1782 <page string="Products">
1783 <button colspan="4" string="Round Qty to SoQ" type="object" name="round_to_soq" icon="gtk-execute" attrs="{'invisible': [('state', '!=', 'draft')]}" />
1784- <field name="order_line" mode="tree,form" colspan="4" nolabel="1" attrs="{'readonly': [('state', 'not in', ['draft', 'validated'])]}">
1785+ <field name="order_line" mode="tree,form" colspan="4" nolabel="1" attrs="{'readonly': [('state', 'not in', ['draft', 'draft_p', 'validated', 'validated_p'])]}">
1786 <tree string="Products" editable="top" >
1787 <field name="product_id" on_change="requested_product_id_change(product_id, comment, parent.categ)"
1788 context="available_for_restriction='consumption',search_default_not_restricted=1"
1789@@ -87,14 +87,15 @@
1790 <button name="open_order_line_to_correct"
1791 string="Configurator"
1792 type="object"
1793- attrs="{'invisible': ['|', ('product_id_ok', '=', True), ('state', 'not in', ['draft', 'validated'])]}"
1794+ attrs="{'invisible': ['|', ('product_id_ok', '=', True), ('state', 'not in', ['draft', 'draft_p', 'validated', 'validated_p'])]}"
1795 icon="terp-go-week" />
1796 <field name="product_uom_qty" on_change="onchange_uom(product_id, product_uom, product_uom_qty)" />
1797- <field name="cost_price" string="Cost Price (FC)" />
1798+ <field name="price_unit"/>
1799 <field name="notes" />
1800+ <field name="display_resourced_orig_line" />
1801 <field name="display_confirm_button" invisible="1" />
1802 <field name="type" on_change="requested_type_change(product_id, type)"
1803- attrs="{'readonly': [('fake_state', 'not in', ['draft', 'validated']), ('display_confirm_button', '=', False)]}"
1804+ attrs="{'readonly': [('fake_state', 'not in', ['draft', 'draft_p', 'validated', 'validated_p']), ('display_confirm_button', '=', False)]}"
1805 />
1806 <field name="my_company_id" invisible="1" />
1807 <!-- utp-357: the supplier will be selected in the sourcing tool -->
1808@@ -103,15 +104,17 @@
1809 attrs="{'readonly': [('type', '=', 'make_to_stock')]}"
1810 domain="[('supplier', '=', True), ('id', '!=', my_company_id), ('check_partner_ir', '=', True)]"
1811 />
1812- <field name="fake_state" invisible="1" />
1813+ <field name="fake_state" invisible="1"/>
1814+ <field name="state_to_display"/>
1815 <field name="stock_take_date"/>
1816 <field name="price_subtotal" sum="Total"/>
1817 <button name="open_split_wizard"
1818 type="object"
1819 string="Split Line"
1820 icon="terp-stock_effects-object-colorize"
1821- attrs="{'invisible': [('fake_state', 'in', ('confirmed', 'cancel', 'done', 'exception'))]}"
1822+ attrs="{'invisible': [('fake_state', 'in', ('confirmed', 'cancel', 'cancel_r', 'done', 'exception'))]}"
1823 />
1824+ <button string="Validate" icon="terp-check" name="validated" type="workflow" attrs="{'invisible': [('fake_state', '!=', 'draft')]}"/>
1825 </tree>
1826 <form>
1827 <field name="product_id" colspan="4" />
1828@@ -126,9 +129,8 @@
1829 </field>
1830 <group colspan="4" col="6">
1831 <field name="state" />
1832- <button name="procurement_cancel" icon="gtk-cancel" string="Cancel" states="draft,validated" context="{'noraise': True}" />
1833- <button name="procurement_validate" icon="terp-check" string="Validate" states="draft" />
1834- <button name="procurement_confirm" icon="gtk-execute" string="Confirm" states="validated" confirm="You are about to confirm the Internal Request without going through the sourcing tool. Are you sure?" />
1835+ <button name="procurement_cancel" icon="gtk-cancel" string="Cancel" states="draft,draft_p,validated,validated_p" context="{'noraise': True}" />
1836+ <button name="validate_lines" type="object" string="Validate Lines" icon="terp-check" states="draft,draft_p" attrs="{'readonly': [('no_line', '=', True)]}" />
1837 </group>
1838 </page>
1839 <page string="Sourcing Documents">
1840
1841=== removed file 'bin/addons/procurement_request/procurement_request_workflow.xml'
1842--- bin/addons/procurement_request/procurement_request_workflow.xml 2015-11-10 13:23:06 +0000
1843+++ bin/addons/procurement_request/procurement_request_workflow.xml 1970-01-01 00:00:00 +0000
1844@@ -1,86 +0,0 @@
1845-<?xml version="1.0" encoding="utf-8" ?>
1846-<openerp>
1847- <data>
1848-
1849- <record id="sale.act_draft" model="workflow.activity">
1850- <field name="flow_start" eval="True" />
1851- <field name="kind">dummy</field>
1852- </record>
1853-
1854- <record id="act_procurement" model="workflow.activity">
1855- <field name="wkf_id" ref="sale.wkf_sale"/>
1856- <field name="flow_start" eval="False" />
1857- <field name="kind">dummy</field>
1858- <field name="name">procurement</field>
1859- </record>
1860-
1861- <record id="act_proc_validate" model="workflow.activity">
1862- <field name="wkf_id" ref="sale.wkf_sale" />
1863- <field name="name">procurement_validate</field>
1864- <field name="kind">function</field>
1865- <field name="action">validate_procurement()</field>
1866- </record>
1867- <record id="act_proc_confirm" model="workflow.activity">
1868- <field name="wkf_id" ref="sale.wkf_sale" />
1869- <field name="name">procurement_confirm</field>
1870- <field name="kind">function</field>
1871- <field name="action">confirm_procurement()</field>
1872- </record>
1873- <record id="act_proc_cancel" model="workflow.activity">
1874- <field name="wkf_id" ref="sale.wkf_sale" />
1875- <field name="name">procurement_cancel</field>
1876- <field name="flow_stop">True</field>
1877- <field name="kind">function</field>
1878- <field name="action">wkf_action_cancel()</field>
1879- </record>
1880- <record id="act_proc_done" model="workflow.activity">
1881- <field name="wkf_id" ref="sale.wkf_sale" />
1882- <field name="flow_stop">True</field>
1883- <field name="name">procurement_done</field>
1884- <field name="kind">function</field>
1885- <field name="action">procurement_done()</field>
1886- </record>
1887-
1888- <record id="trans_proc_draft" model="workflow.transition">
1889- <field name="act_from" ref="sale.act_draft"/>
1890- <field name="act_to" ref="act_procurement"/>
1891- <field name="condition">procurement_request==True</field>
1892- </record>
1893-
1894- <record id="trans_proc_validate" model="workflow.transition">
1895- <field name="act_from" ref="act_procurement"/>
1896- <field name="act_to" ref="act_proc_validate"/>
1897- <field name="condition">procurement_request==True</field>
1898- <field name="signal">procurement_validate</field>
1899- </record>
1900- <record id="trans_proc_validate_confirm" model="workflow.transition">
1901- <field name="act_from" ref="act_proc_validate"/>
1902- <field name="act_to" ref="act_proc_confirm"/>
1903- <field name="condition">procurement_request==True</field>
1904- <field name="signal">procurement_confirm</field>
1905- </record>
1906- <record id="trans_validate_cancel" model="workflow.transition">
1907- <field name="act_from" ref="act_proc_validate"/>
1908- <field name="act_to" ref="act_proc_cancel"/>
1909- <field name="signal">procurement_cancel</field>
1910- </record>
1911- <record id="trans_procurement_cancel" model="workflow.transition">
1912- <field name="act_from" ref="act_procurement"/>
1913- <field name="act_to" ref="act_proc_cancel"/>
1914- <field name="signal">procurement_cancel</field>
1915- </record>
1916- <record id="trans_confirm_proc_cancel" model="workflow.transition">
1917- <field name="act_from" ref="act_proc_confirm"/>
1918- <field name="act_to" ref="act_proc_cancel"/>
1919- <field name="signal">procurement_cancel</field>
1920- </record>
1921- <record id="trans_confirm_proc_done" model="workflow.transition">
1922- <field name="act_from" ref="act_proc_confirm"/>
1923- <field name="act_to" ref="act_proc_done"/>
1924- <field name="trigger_model">procurement.order</field>
1925- <field name="trigger_expr_id">procurement_lines_get()</field>
1926- <field name="condition"> test_state_done('finished')</field>
1927- </record>
1928-
1929- </data>
1930-</openerp>
1931
1932=== modified file 'bin/addons/purchase/__init__.py'
1933--- bin/addons/purchase/__init__.py 2011-01-14 00:11:01 +0000
1934+++ bin/addons/purchase/__init__.py 2017-10-02 12:34:11 +0000
1935@@ -20,12 +20,14 @@
1936 ##############################################################################
1937
1938 import purchase
1939+import purchase_line
1940+import purchase_workflow
1941 import partner
1942 import stock
1943 import wizard
1944 import report
1945 import stock
1946 import company
1947-
1948+import procurement_order
1949 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1950
1951
1952=== modified file 'bin/addons/purchase/__openerp__.py'
1953--- bin/addons/purchase/__openerp__.py 2012-05-25 13:29:26 +0000
1954+++ bin/addons/purchase/__openerp__.py 2017-10-02 12:34:11 +0000
1955@@ -39,13 +39,13 @@
1956 'data': [
1957 'security/purchase_security.xml',
1958 'security/ir.model.access.csv',
1959- 'purchase_workflow.xml',
1960 'purchase_sequence.xml',
1961 'company_view.xml',
1962 'purchase_data.xml',
1963 'wizard/purchase_order_group_view.xml',
1964 'wizard/purchase_installer.xml',
1965 'wizard/purchase_line_invoice_view.xml',
1966+ 'wizard/purchase_line_cancel_view.xml',
1967 'purchase_report.xml',
1968 'purchase_view.xml',
1969 'stock_view.xml',
1970@@ -53,9 +53,10 @@
1971 #'process/purchase_process.xml',
1972 'report/purchase_report_view.xml',
1973 'board_purchase_view.xml',
1974+ 'purchase_line_workflow.xml',
1975 ],
1976 'test': [
1977- 'test/purchase_from_order.yml',
1978+ 'test/purchase_from_order.yml',
1979 'test/purchase_from_manual.yml',
1980 'test/purchase_from_picking.yml',
1981 'purchase_unit_test.xml',
1982
1983=== modified file 'bin/addons/purchase/process/purchase_process.xml'
1984--- bin/addons/purchase/process/purchase_process.xml 2011-01-14 00:11:01 +0000
1985+++ bin/addons/purchase/process/purchase_process.xml 2017-10-02 12:34:11 +0000
1986@@ -157,7 +157,7 @@
1987 <field model="process.node" name="target_node_id" ref="account.process_node_supplierdraftinvoices0"/>
1988 <field model="process.node" name="source_node_id" ref="process_node_productrecept0"/>
1989 </record>
1990-
1991+ <!--
1992 <record id="process_transition_confirmingpurchaseorder0" model="process.transition">
1993 <field eval="[(6,0,[])]" name="transition_ids"/>
1994 <field eval="&quot;&quot;&quot;Confirmation&quot;&quot;&quot;" name="name"/>
1995@@ -166,6 +166,7 @@
1996 <field model="process.node" name="source_node_id" ref="process_node_draftpurchaseorder0"/>
1997 <field eval="[(6,0,[ref('purchase.trans_draft_confirmed')])]" name="transition_ids"/>
1998 </record>
1999+ -->
2000
2001 <record id="process_transition_confirmingpurchaseorder1" model="process.transition">
2002 <field eval="[(6,0,[])]" name="transition_ids"/>
2003
2004=== added file 'bin/addons/purchase/procurement_order.py'
2005--- bin/addons/purchase/procurement_order.py 1970-01-01 00:00:00 +0000
2006+++ bin/addons/purchase/procurement_order.py 2017-10-02 12:34:11 +0000
2007@@ -0,0 +1,152 @@
2008+# -*- coding: utf-8 -*-
2009+
2010+from datetime import datetime
2011+from dateutil.relativedelta import relativedelta
2012+
2013+from osv import osv, fields
2014+
2015+
2016+class procurement_order(osv.osv):
2017+ _inherit = 'procurement.order'
2018+ _columns = {
2019+ 'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
2020+ }
2021+
2022+ def action_po_assign(self, cr, uid, ids, context=None):
2023+ """ This is action which call from workflow to assign purchase order to procurements
2024+ @return: True
2025+ """
2026+ res = self.make_po(cr, uid, ids, context=context)
2027+ res = res.values()
2028+ return len(res) and res[0] or 0 #TO CHECK: why workflow is generated error if return not integer value
2029+
2030+ def get_partner_hook(self, cr, uid, ids, context=None, *args, **kwargs):
2031+ '''
2032+ return a dictionary with partner, seller_qty and seller_delay
2033+ '''
2034+ result = {}
2035+
2036+ procurement = kwargs['procurement']
2037+ partner = procurement.product_id.seller_id # Taken Main Supplier of Product of Procurement.
2038+ seller_qty = procurement.product_id.seller_qty
2039+ seller_delay = int(procurement.product_id.seller_delay)
2040+
2041+ result.update(partner=partner,
2042+ seller_qty=seller_qty,
2043+ seller_delay=seller_delay)
2044+
2045+ return result
2046+
2047+ def po_line_values_hook(self, cr, uid, ids, context=None, *args, **kwargs):
2048+ '''
2049+ Please copy this to your module's method also.
2050+ This hook belongs to the make_po method from purchase>purchase.py>procurement_order
2051+
2052+ - allow to modify the data for purchase order line creation
2053+ '''
2054+ line = kwargs['line']
2055+ return line
2056+
2057+ def po_values_hook(self, cr, uid, ids, context=None, *args, **kwargs):
2058+ '''
2059+ Please copy this to your module's method also.
2060+ This hook belongs to the make_po method from purchase>purchase.py>procurement_order
2061+
2062+ - allow to modify the data for purchase order creation
2063+ '''
2064+ values = kwargs['values']
2065+ return values
2066+
2067+ def create_po_hook(self, cr, uid, ids, context=None, *args, **kwargs):
2068+ '''
2069+ creation of purchase order
2070+ return the id of newly created po
2071+ '''
2072+ po_obj = self.pool.get('purchase.order')
2073+ values = kwargs['values']
2074+ purchase_id = po_obj.create(cr, uid, values, context=context)
2075+ return purchase_id
2076+
2077+ def make_po(self, cr, uid, ids, context=None):
2078+ """ Make purchase order from procurement
2079+ @return: New created Purchase Orders procurement wise
2080+ """
2081+ res = {}
2082+ if context is None:
2083+ context = {}
2084+ company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
2085+ partner_obj = self.pool.get('res.partner')
2086+ uom_obj = self.pool.get('product.uom')
2087+ pricelist_obj = self.pool.get('product.pricelist')
2088+ prod_obj = self.pool.get('product.product')
2089+ acc_pos_obj = self.pool.get('account.fiscal.position')
2090+ for procurement in self.browse(cr, uid, ids, context=context):
2091+ res_id = procurement.move_id.id
2092+
2093+ # partner, seller_qty and seller_delay are computed with hook
2094+ hook = self.get_partner_hook(cr, uid, ids, context=context, procurement=procurement)
2095+ partner = hook['partner']
2096+ seller_qty = hook['seller_qty']
2097+ seller_delay = hook['seller_delay']
2098+ partner_id = partner.id
2099+ address_id = partner_obj.address_get(cr, uid, [partner_id], ['delivery'])['delivery']
2100+ pricelist_id = partner.property_product_pricelist_purchase
2101+
2102+ uom_id = procurement.product_id.uom_po_id.id
2103+
2104+ qty = uom_obj._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
2105+ if seller_qty:
2106+ qty = max(qty,seller_qty)
2107+
2108+ price = pricelist_obj.price_get(cr, uid, [pricelist_id.id], procurement.product_id.id, qty, partner_id, {'uom': uom_id})[pricelist_id.id]
2109+ if hook.get('price_unit', False):
2110+ price = hook.get('price_unit', False)
2111+
2112+ newdate = datetime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
2113+ newdate = (newdate - relativedelta(days=int(company.po_lead))) - relativedelta(days=int(seller_delay))
2114+
2115+ #Passing partner_id to context for purchase order line integrity of Line name
2116+ context.update({'lang': partner.lang, 'partner_id': partner_id})
2117+
2118+ product = prod_obj.browse(cr, uid, procurement.product_id.id, context=context)
2119+
2120+ line = {
2121+ 'name': product.partner_ref,
2122+ 'product_qty': qty,
2123+ 'product_id': procurement.product_id.id,
2124+ 'product_uom': uom_id,
2125+ 'price_unit': price,
2126+ 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
2127+ 'move_dest_id': res_id,
2128+ 'notes': product.description_purchase,
2129+ }
2130+
2131+ # line values modification from hook
2132+ line = self.po_line_values_hook(cr, uid, ids, context=context, line=line, procurement=procurement, pricelist=pricelist_id)
2133+
2134+ taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
2135+ taxes = acc_pos_obj.map_tax(cr, uid, partner.property_account_position, taxes_ids)
2136+ line.update({
2137+ 'taxes_id': [(6,0,taxes)]
2138+ })
2139+ values = {
2140+ 'origin': procurement.origin,
2141+ 'partner_id': partner_id,
2142+ 'partner_address_id': address_id,
2143+ 'location_id': procurement.location_id.id,
2144+ 'pricelist_id': pricelist_id.id,
2145+ 'order_line': [(0,0,line)],
2146+ 'company_id': procurement.company_id.id,
2147+ 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False,
2148+ }
2149+ # values modification from hook
2150+ values = self.po_values_hook(cr, uid, ids, context=context, values=values, procurement=procurement, line=line,)
2151+ # purchase creation from hook
2152+ purchase_id = self.create_po_hook(cr, uid, ids, context=context, values=values, procurement=procurement)
2153+ res[procurement.id] = purchase_id
2154+ self.write(cr, uid, [procurement.id], {'state': 'running', 'purchase_id': purchase_id})
2155+ return res
2156+
2157+procurement_order()
2158+
2159+
2160
2161=== modified file 'bin/addons/purchase/purchase.py'
2162--- bin/addons/purchase/purchase.py 2017-08-03 15:16:40 +0000
2163+++ bin/addons/purchase/purchase.py 2017-10-02 12:34:11 +0000
2164@@ -20,28 +20,38 @@
2165 ##############################################################################
2166
2167 import time
2168-from datetime import datetime
2169-from dateutil.relativedelta import relativedelta
2170-
2171 from osv import osv, fields
2172 import netsvc
2173-import pooler
2174 from tools.translate import _
2175 import decimal_precision as dp
2176 from osv.orm import browse_record, browse_null
2177-
2178-#
2179-# Model definition
2180-#
2181+from order_types import ORDER_PRIORITY, ORDER_CATEGORY
2182+import logging
2183+import pooler
2184+import json
2185+import threading
2186+from datetime import datetime
2187+from dateutil.relativedelta import relativedelta
2188+from workflow.wkf_expr import _eval_expr
2189+from purchase_override import PURCHASE_ORDER_STATE_SELECTION
2190+from account_override.period import get_period_from_date
2191+from account_override.period import get_date_in_period
2192+
2193+ORDER_TYPES_SELECTION = [
2194+ ('regular', _('Regular')),
2195+ ('donation_exp', _('Donation before expiry')),
2196+ ('donation_st', _('Standard donation')),
2197+ ('loan', _('Loan')),
2198+ ('in_kind', _('In Kind Donation')),
2199+ ('purchase_list', _('Purchase List')),
2200+ ('direct', _('Direct Purchase Order')),
2201+]
2202+
2203+
2204 class purchase_order(osv.osv):
2205-
2206- def _calc_amount(self, cr, uid, ids, prop, unknow_none, unknow_dict):
2207- res = {}
2208- for order in self.browse(cr, uid, ids):
2209- res[order.id] = 0
2210- for oline in order.order_line:
2211- res[order.id] += oline.price_unit * oline.product_qty
2212- return res
2213+ _name = "purchase.order"
2214+ _description = "Purchase Order"
2215+ _order = "name desc"
2216
2217 def _amount_all(self, cr, uid, ids, field_name, arg, context=None):
2218 res = {}
2219@@ -91,19 +101,41 @@
2220 res[purchase.id]=min_date
2221 return res
2222
2223-
2224 def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
2225 res = {}
2226+ sp_obj = self.pool.get('stock.picking')
2227+ inv_obj = self.pool.get('account.invoice')
2228 for purchase in self.browse(cursor, user, ids, context=context):
2229- tot = 0.0
2230- for invoice in purchase.invoice_ids:
2231- if invoice.state not in ('draft','cancel'):
2232- tot += invoice.amount_untaxed
2233-
2234- if purchase.amount_untaxed:
2235- res[purchase.id] = min(100.0, tot * 100.0 / (purchase.amount_untaxed))
2236+ if ((purchase.order_type == 'regular' and purchase.partner_id.partner_type in ('internal', 'esc')) or \
2237+ purchase.order_type in ['donation_exp', 'donation_st', 'loan', 'in_kind']):
2238+ res[purchase.id] = purchase.shipped_rate
2239 else:
2240- res[purchase.id] = 0.0
2241+ tot = 0.0
2242+ # UTP-808: Deleted invoices amount should be taken in this process. So what we do:
2243+ # 1/ Take all closed stock picking linked to the purchase
2244+ # 2/ Search invoices linked to these stock picking
2245+ # 3/ Take stock picking not linked to an invoice
2246+ # 4/ Use these non-invoiced closed stock picking to add their amount to the "invoiced" amount
2247+ for invoice in purchase.invoice_ids:
2248+ if invoice.state not in ('draft','cancel'):
2249+ tot += invoice.amount_untaxed
2250+ stock_pickings = sp_obj.search(cursor, user, [('purchase_id', '=', purchase.id), ('state', '=', 'done')])
2251+ if stock_pickings:
2252+ sp_ids = list(stock_pickings)
2253+ if isinstance(stock_pickings, (int, long)):
2254+ stock_pickings = [stock_pickings]
2255+ for sp in stock_pickings:
2256+ inv_ids = inv_obj.search_exist(cursor, user, [('picking_id', '=', sp)])
2257+ if inv_ids:
2258+ sp_ids.remove(sp)
2259+ if sp_ids:
2260+ for stock_picking in sp_obj.browse(cursor, user, sp_ids):
2261+ for line in stock_picking.move_lines:
2262+ tot += line.product_qty * line.price_unit
2263+ if purchase.amount_untaxed:
2264+ res[purchase.id] = min(100.0, tot * 100.0 / (purchase.amount_untaxed))
2265+ else:
2266+ res[purchase.id] = 0.0
2267 return res
2268
2269 def _shipped_rate(self, cr, uid, ids, name, arg, context=None):
2270@@ -140,61 +172,399 @@
2271 result[line.order_id.id] = True
2272 return result.keys()
2273
2274+ def _get_allocation_setup(self, cr, uid, ids, field_name, args, context=None):
2275+ '''
2276+ Returns the Unifield configuration value
2277+ '''
2278+ res = {}
2279+ setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
2280+
2281+ for order in ids:
2282+ res[order] = setup.allocation_setup
2283+
2284+ return res
2285+
2286+ def _get_no_line(self, cr, uid, ids, field_name, args, context=None):
2287+ """
2288+ Compute the number of Purchase order lines in each purchase order.
2289+ A split line is count as one line
2290+ :param cr: Cursor to the database
2291+ :param uid: ID of the res.users that calls this method
2292+ :param ids: List of purchase.order ID to compute
2293+ :param field_name: Name of the field to compute
2294+ :param args: Extra parameters
2295+ :param context: Context of the call
2296+ :return: A dictionnary with the purchase.order ID as keys and the number of Purchase
2297+ order lines for each of them as value
2298+ """
2299+ pol_obj = self.pool.get('sale.order.line')
2300+
2301+ if context is None:
2302+ context = {}
2303+
2304+ if isinstance(ids, (int, long)):
2305+ ids = [ids]
2306+
2307+ res = {}
2308+
2309+ for order_id in ids:
2310+ res[order_id] = pol_obj.search_count(cr, uid, [
2311+ ('order_id', '=', order_id),
2312+ ('is_line_split', '=', False),
2313+ ], context=context)
2314+
2315+ return res
2316+
2317+
2318+ def _po_from_x(self, cr, uid, ids, field_name, args, context=None):
2319+ """
2320+ fields.function multi for 'po_from_ir' and 'po_from_fo' fields.
2321+ As one PO can contains lines from IR and from FO, both fields can be True
2322+ """
2323+ if context is None:
2324+ context = {}
2325+ if isinstance(ids, (int,long)):
2326+ ids = [ids]
2327+
2328+ res = {}
2329+ for po in self.browse(cr, uid, ids, context=context):
2330+ res[po.id] = {
2331+ 'po_from_ir': False,
2332+ 'po_from_fo': False,
2333+ }
2334+
2335+ return res
2336+
2337+ def _get_dest_partner_names(self, cr, uid, ids, field_name, args, context=None):
2338+ res = {}
2339+ res_partner_obj = self.pool.get('res.partner')
2340+ for po_r in self.read(cr, uid, ids, ['dest_partner_ids'], context=context):
2341+ names = ''
2342+ if po_r['dest_partner_ids']:
2343+ name_tuples = res_partner_obj.name_get(cr, uid, po_r['dest_partner_ids'], context=context)
2344+ if name_tuples:
2345+ names_list = [nt[1] for nt in name_tuples]
2346+ names = "; ".join(names_list)
2347+ res[po_r['id']] = names
2348+ return res
2349+
2350+ def _get_project_ref(self, cr, uid, ids, field_name, args, context=None):
2351+ '''
2352+ Get the name of the POs at project side
2353+ '''
2354+ if isinstance(ids, (int, long)):
2355+ ids = [ids]
2356+
2357+ res = {}
2358+ so_obj = self.pool.get('sale.order')
2359+ for po in ids:
2360+ res[po] = {
2361+ 'fnct_project_ref': '',
2362+ 'sourced_references': '',
2363+ }
2364+
2365+ so_ids = self.get_so_ids_from_po_ids(cr, uid, po, context=context)
2366+ for so in so_obj.read(cr, uid, so_ids, ['client_order_ref', 'name'], context=context):
2367+ if so['client_order_ref']:
2368+ if res[po]['fnct_project_ref']:
2369+ res[po]['fnct_project_ref'] += ' - '
2370+ res[po]['fnct_project_ref'] += so['client_order_ref']
2371+
2372+ if res[po]['sourced_references']:
2373+ res[po]['sourced_references'] += ','
2374+ res[po]['sourced_references'] += so['name']
2375+
2376+ return res
2377+
2378+ def _get_vat_ok(self, cr, uid, ids, field_name, args, context=None):
2379+ '''
2380+ Return True if the system configuration VAT management is set to True
2381+ '''
2382+ vat_ok = self.pool.get('unifield.setup.configuration').get_config(cr, uid).vat_ok
2383+ res = {}
2384+ for id in ids:
2385+ res[id] = vat_ok
2386+
2387+ return res
2388+
2389+ def _get_requested_date_in_past(self, cr, uid, ids, field_name, args, context=None):
2390+ if isinstance(ids, (int, long)):
2391+ ids = [ids]
2392+
2393+ res = {}
2394+ for po in self.read(cr, uid, ids, ['delivery_requested_date', 'rfq_ok'], context=context):
2395+ res[po['id']] = po['delivery_requested_date'] and not po['rfq_ok'] and po['delivery_requested_date'] < time.strftime('%Y-%m-%d') or False
2396+
2397+ return res
2398+
2399+
2400 def _invoiced(self, cursor, user, ids, name, arg, context=None):
2401 res = {}
2402- for purchase in self.browse(cursor, user, ids, context=context):
2403+ for purchase in self.read(cursor, user, ids, ['invoiced_rate'], context=context):
2404 invoiced = False
2405- if purchase.invoiced_rate == 100.00:
2406+ if purchase['invoiced_rate'] == 100.00:
2407 invoiced = True
2408- res[purchase.id] = invoiced
2409- return res
2410-
2411- STATE_SELECTION = [
2412- ('draft', 'Request for Quotation'),
2413- ('wait', 'Waiting'),
2414- ('confirmed', 'Waiting Approval'),
2415- ('approved', 'Approved'),
2416- ('except_picking', 'Shipping Exception'),
2417- ('except_invoice', 'Invoice Exception'),
2418- ('done', 'Done'),
2419- ('cancel', 'Cancelled')
2420- ]
2421+ res[purchase['id']] = invoiced
2422+ return res
2423+
2424+ def _src_customer_ref(self, cr, uid, obj, name, args, context=None):
2425+ '''
2426+ return a domain when user filter on the customer_ref field
2427+ '''
2428+ if not args:
2429+ return []
2430+
2431+ po_ids = set()
2432+ for tu in args:
2433+ if tu[1] in ('ilike', 'not ilike', '=', '!='):
2434+ so_ids = self.pool.get('sale.order').search(cr, uid, [('client_order_ref', tu[1], tu[2])], context=context)
2435+ sol_ids = self.pool.get('sale.order.line').search(cr, uid, [('order_id', 'in', so_ids)], context=context)
2436+ pol_ids = self.pool.get('purchase.order.line').search(cr, uid, [('linked_sol_id', 'in', sol_ids)], context=context)
2437+ for pol in self.pool.get('purchase.order.line').browse(cr, uid, pol_ids, context=context):
2438+ po_ids.add(pol.order_id.id)
2439+ else:
2440+ raise osv.except_osv(_('Error'), _('Bad operator : You can only use \'=\', \'!=\', \'ilike\' or \'not ilike\' as operator'))
2441+
2442+ return [('id', 'in', list(po_ids))]
2443+
2444+ def _get_customer_ref(self, cr, uid, ids, field_name, args, context=None):
2445+ '''
2446+ Return a concatenation of the PO's customer references from the project (case of procurement request)
2447+ '''
2448+ if isinstance(ids, (int, long)):
2449+ ids = [ids]
2450+
2451+ res = {}
2452+ so_obj = self.pool.get('sale.order')
2453+ for po_id in ids:
2454+ res[po_id] = ""
2455+
2456+ so_ids = self.get_so_ids_from_po_ids(cr, uid, po_id, context=context)
2457+ for so in so_obj.read(cr, uid, so_ids, ['client_order_ref'], context=context):
2458+ if so['client_order_ref']:
2459+ if res[po_id]:
2460+ res[po_id] += ';'
2461+ res[po_id] += so['client_order_ref']
2462+
2463+ return res
2464+
2465+ def _get_line_count(self, cr, uid, ids, field_name, args, context=None):
2466+ '''
2467+ Return the number of line(s) for the PO
2468+ '''
2469+ pol_obj = self.pool.get('purchase.order.line')
2470+
2471+ if isinstance(ids, (int, long)):
2472+ ids = [ids]
2473+
2474+ res = {}.fromkeys(ids, 0)
2475+ line_number_by_order = {}
2476+
2477+ lines = pol_obj.search(cr, uid, [('order_id', 'in', ids)], context=context)
2478+ for l in pol_obj.read(cr, uid, lines, ['order_id', 'line_number'], context=context):
2479+ line_number_by_order.setdefault(l['order_id'][0], set())
2480+ line_number_by_order[l['order_id'][0]].add(l['line_number'])
2481+
2482+ for po_id, ln in line_number_by_order.iteritems():
2483+ res[po_id] = len(ln)
2484+
2485+ return res
2486+
2487+
2488+ def _get_less_advanced_pol_state(self, cr, uid, ids, field_name, arg, context=None):
2489+ """
2490+ Get the less advanced state of the purchase order lines
2491+ Used to compute sale order state
2492+ """
2493+ if context is None:
2494+ context = {}
2495+ if isinstance(ids, (int, long)):
2496+ ids = [ids]
2497+
2498+ res = {}
2499+ for po in self.browse(cr, uid, ids, context=context):
2500+ pol_states = set([line.state for line in po.order_line])
2501+ if all([s.startswith('cancel') for s in pol_states]): # if all lines are cancelled then the PO is cancelled
2502+ res[po.id] = 'cancel'
2503+ else: # else compute the less advanced state:
2504+ # cancel state must be ignored:
2505+ pol_states.discard('cancel')
2506+ pol_states.discard('cancel_r')
2507+ res[po.id] = self.pool.get('purchase.order.line.state').get_less_advanced_state(cr, uid, ids, pol_states, context=context)
2508+
2509+ if res[po.id] == 'draft': # set the draft-p state ?
2510+ draft_sequence = self.pool.get('purchase.order.line.state').get_sequence(cr, uid, ids, 'draft', context=context)
2511+ # do we have a line further then draft in our FO ?
2512+ if any([self.pool.get('purchase.order.line.state').get_sequence(cr, uid, ids, s, context=context) > draft_sequence for s in pol_states]):
2513+ res[po.id] = 'draft_p'
2514+ elif res[po.id] in ('validated', 'validated_n'): # set the validated-p state ?
2515+ validated_sequence = self.pool.get('purchase.order.line.state').get_sequence(cr, uid, ids, 'validated', context=context)
2516+ # do we have a line further then validated in our FO ?
2517+ if any([self.pool.get('purchase.order.line.state').get_sequence(cr, uid, ids, s, context=context) > validated_sequence for s in pol_states]):
2518+ res[po.id] = 'validated_p'
2519+ else:
2520+ res[po.id] = 'validated'
2521+ elif res[po.id].startswith('sourced'): # set the sourced-p state ?
2522+ sourced_sequence = self.pool.get('purchase.order.line.state').get_sequence(cr, uid, ids, 'sourced', context=context)
2523+ # do we have a line further then sourced in our FO ?
2524+ if any([self.pool.get('purchase.order.line.state').get_sequence(cr, uid, ids, s, context=context) > sourced_sequence for s in pol_states]):
2525+ res[po.id] = 'sourced_p'
2526+ else:
2527+ res[po.id] = 'sourced'
2528+ elif res[po.id] == 'confirmed': # set the confirmed-p state ?
2529+ confirmed_sequence = self.pool.get('purchase.order.line.state').get_sequence(cr, uid, ids, 'confirmed', context=context)
2530+ # do we have a line further then confirmed in our FO ?
2531+ if any([self.pool.get('purchase.order.line.state').get_sequence(cr, uid, ids, s, context=context) > confirmed_sequence for s in pol_states]):
2532+ res[po.id] = 'confirmed_p'
2533+
2534+ return res
2535+
2536+ def _is_fixed_type(self, cr, uid, ids, field_name, args, context=None):
2537+ """
2538+ For each PO, set is the Order Type of the PO can be changed or not
2539+ :param cr: Cursor to the database
2540+ :param uid: ID of the res.users that calls this method
2541+ :param ids: List of ID of purchase.order records to check
2542+ :param field_name: Name of the field to compute
2543+ :param args: Extra parameters
2544+ :param context: Context of the call
2545+ :return: A dictionnary with ID of the purchase.order record as keys and True/Fales as values
2546+ """
2547+ if isinstance(ids, (int, long)):
2548+ ids = [ids]
2549+
2550+ if context is None:
2551+ context = {}
2552+
2553+ context['procurement_request'] = True
2554+
2555+ res = {}
2556+ for po in self.browse(cr, uid, ids, fields_to_fetch=['po_from_fo', 'po_from_ir'], context=context):
2557+ if po.po_from_fo or po.po_from_ir:
2558+ src_type = set()
2559+ sale_ids = self.get_so_ids_from_po_ids(cr, uid, [po.id], context=context)
2560+ if sale_ids:
2561+ for sale in self.pool.get('sale.order').read(cr, uid, sale_ids, ['procurement_request', 'order_type'], context=context):
2562+ if sale['procurement_request'] or sale['order_type'] == 'regular':
2563+ src_type.add('regular')
2564+ src_type.add('purchase_list')
2565+
2566+ if not sale['procurement_request']:
2567+ if sale['order_type'] == 'regular':
2568+ src_type.add('direct')
2569+ elif sale['order_type'] == 'loan':
2570+ src_type.add('loan')
2571+ elif sale['order_type'] == 'donation_exp':
2572+ src_type.add('donation_exp')
2573+ elif sale['order_type'] == 'donation_st':
2574+ src_type.add('donation_st')
2575+ res[po.id] = json.dumps(list(src_type))
2576+ else:
2577+ res[po.id] = json.dumps([x[0] for x in ORDER_TYPES_SELECTION])
2578+
2579+ return res
2580+
2581+ def _order_line_order_type(self, cr, uid, ids, context=None):
2582+ """
2583+ Return the list of ID of purchase.order records to update
2584+ :param cr: Cursor to the database
2585+ :param uid: ID of the res.users that calls this method
2586+ :param ids: List of ID of purchase.order.line records updated
2587+ :param context: Context of the call
2588+ :return: A list that represents a domain to apply on purchase.order records
2589+ """
2590+ lines = self.read(cr, uid, ids, ['order_id'], context=context)
2591+ po_ids = set()
2592+ for l in lines:
2593+ po_ids.add(l['order_id'][0])
2594+
2595+ return list(po_ids)
2596+
2597
2598 _columns = {
2599- 'name': fields.char('Order Reference', size=64, required=True, select=True, help="unique number of the purchase order,computed automatically when the purchase order is created"),
2600- 'origin': fields.char('Source Document', size=512,
2601- help="Reference of the document that generated this purchase order request."
2602- ),
2603- 'partner_ref': fields.char('Supplier Reference', states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, size=64),
2604- 'date_order':fields.date('Date Ordered', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}, select=True, help="Date on which this document has been created."),
2605+ 'order_type': fields.selection(ORDER_TYPES_SELECTION, string='Order Type', required=True, states={'sourced':[('readonly',True)], 'split':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
2606+ 'loan_id': fields.many2one('sale.order', string='Linked loan', readonly=True),
2607+ 'priority': fields.selection(ORDER_PRIORITY, string='Priority', states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
2608+ 'categ': fields.selection(ORDER_CATEGORY, string='Order category', required=True, states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
2609+ # we increase the size of the 'details' field from 30 to 86
2610+ 'details': fields.char(size=86, string='Details', states={'sourced':[('readonly',True)], 'split':[('readonly',True)], 'cancel':[('readonly',True)], 'confirmed_wait':[('readonly',True)], 'validated':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
2611+ 'loan_duration': fields.integer(string='Loan duration', help='Loan duration in months', states={'validated':[('readonly',True)],'approved':[('readonly',True)],'done':[('readonly',True)]}),
2612+ 'date_order': fields.date(string='Creation Date', readonly=True, required=True,
2613+ states={'draft':[('readonly',False)],}, select=True, help="Date on which this document has been created."),
2614+ 'name': fields.char('Order Reference', size=64, required=True, select=True, readonly=True,
2615+ help="unique number of the purchase order,computed automatically when the purchase order is created"),
2616+ 'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', help="Invoices generated for a purchase order", readonly=True),
2617+ 'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft':[('readonly',False)], 'validated': [('readonly',False)]}),
2618+ 'partner_id': fields.many2one('res.partner', 'Supplier', required=True, states={'sourced':[('readonly',True)], 'split':[('readonly',True)], 'rfq_sent':[('readonly',True)], 'rfq_done':[('readonly',True)], 'rfq_updated':[('readonly',True)], 'confirmed':[('readonly',True)], 'confirmed_wait':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)],'cancel':[('readonly',True)]}, change_default=True, domain="[('id', '!=', company_id)]"),
2619+ 'partner_address_id': fields.many2one('res.partner.address', 'Address', required=True,
2620+ states={'sourced':[('readonly',True)], 'split':[('readonly',True)], 'rfq_sent':[('readonly',True)], 'rfq_done':[('readonly',True)], 'rfq_updated':[('readonly',True)], 'validated':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},domain="[('partner_id', '=', partner_id)]"),
2621+ 'dest_partner_id': fields.many2one('res.partner', string='Destination partner'),
2622+ 'invoice_address_id': fields.many2one('res.partner.address', string='Invoicing address', required=True,
2623+ help="The address where the invoice will be sent."),
2624+ 'invoice_method': fields.selection([('manual','Manual'),('order','From Order'),('picking','From Picking')], 'Invoicing Control', required=True, readonly=True,
2625+ help="From Order: a draft invoice will be pre-generated based on the purchase order. The accountant " \
2626+ "will just have to validate this invoice for control.\n" \
2627+ "From Picking: a draft invoice will be pre-generated based on validated receptions.\n" \
2628+ "Manual: allows you to generate suppliers invoices by chosing in the uninvoiced lines of all manual purchase orders."
2629+ ),
2630+ 'merged_line_ids': fields.one2many('purchase.order.merged.line', 'order_id', string='Merged line'),
2631+ 'date_confirm': fields.date(string='Confirmation date'),
2632+ 'allocation_setup': fields.function(_get_allocation_setup, type='selection',
2633+ selection=[('allocated', 'Allocated'),
2634+ ('unallocated', 'Unallocated'),
2635+ ('mixed', 'Mixed')], string='Allocated setup', method=True, store=False),
2636+ 'unallocation_ok': fields.boolean(string='Unallocated PO'),
2637+ 'partner_ref': fields.char('Supplier Reference', size=128),
2638+ 'product_id': fields.related('order_line', 'product_id', type='many2one', relation='product.product', string='Product'),
2639+ 'no_line': fields.function(_get_no_line, method=True, type='boolean', string='No line'),
2640+ 'active': fields.boolean('Active', readonly=True),
2641+ 'po_from_ir': fields.function(_po_from_x, method=True, type='boolean', string='Is PO from IR ?', multi='po_from_x'),
2642+ 'po_from_fo': fields.function(_po_from_x, method=True, type='boolean', string='Is PO from FO ?', multi='po_from_x'),
2643+ 'canceled_end': fields.boolean(string='Canceled End', readonly=True),
2644+ 'is_a_counterpart': fields.boolean('Counterpart?', help="This field is only for indicating that the order is a counterpart"),
2645+ 'po_updated_by_sync': fields.boolean('PO updated by sync', readonly=False),
2646+ 'origin': fields.text('Source Document', help="Reference of the document that generated this purchase order request."),
2647+ # UF-2267: Store also the parent PO as reference in the sourced PO
2648+ 'parent_order_name': fields.many2one('purchase.order', string='Parent PO name', help='If the PO is created from a re-source FO, this field contains the relevant original PO name'),
2649+ 'project_ref': fields.char(size=256, string='Project Ref.'),
2650+ 'message_esc': fields.text(string='ESC Message'),
2651+ 'fnct_project_ref': fields.function(_get_project_ref, method=True, string='Project Ref.',
2652+ type='char', size=256, store=False, multi='so_info'),
2653+ 'dest_partner_ids': fields.many2many('res.partner', 'res_partner_purchase_order_rel', 'purchase_order_id', 'partner_id', 'Customers'), # uf-2223
2654+ 'dest_partner_names': fields.function(_get_dest_partner_names, type='char', size=256, string='Customers', method=True), # uf-2223
2655+ 'split_po': fields.boolean('Created by split PO', readonly=True),
2656+ 'sourced_references': fields.function(_get_project_ref, method=True, string='Sourced references', type='text', store=False, multi='so_info'),
2657+ 'vat_ok': fields.function(_get_vat_ok, method=True, type='boolean', string='VAT OK', store=False, readonly=True),
2658+ 'requested_date_in_past': fields.function(_get_requested_date_in_past, method=True, string='Requested date in past', type='boolean', store=False),
2659+ 'update_in_progress': fields.boolean(string='Update in progress', readonly=True),
2660+ # US-1765: register the 1st call of wkf_confirm_trigger to prevent recursion error
2661+ 'po_confirmed': fields.boolean('PO', readonly=True),
2662+ 'customer_ref': fields.function(_get_customer_ref, fnct_search=_src_customer_ref, method=True, string='Customer Ref.', type='text', store=False),
2663+ 'line_count': fields.function(_get_line_count, method=True, type='integer', string="Line count", store=False),
2664+
2665 'date_approve':fields.date('Date Approved', readonly=1, select=True, help="Date on which purchase order has been approved"),
2666- 'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, change_default=True),
2667- 'partner_address_id':fields.many2one('res.partner.address', 'Address', required=True,
2668- states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},domain="[('partner_id', '=', partner_id)]"),
2669 'dest_address_id':fields.many2one('res.partner.address', 'Destination Address',
2670- states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},
2671+ states={'validated':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},
2672 help="Put an address if you want to deliver directly from the supplier to the customer." \
2673 "In this case, it will remove the warehouse link and set the customer location."
2674 ),
2675- 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
2676+ 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', states={'validated':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
2677 'location_id': fields.many2one('stock.location', 'Destination', required=True, domain=[('usage','<>','view')]),
2678- 'pricelist_id':fields.many2one('product.pricelist', 'Pricelist', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, help="The pricelist sets the currency used for this purchase order. It also computes the supplier price for the selected products/quantities."),
2679- 'state': fields.selection(STATE_SELECTION, 'State', readonly=True, help="The state of the purchase order or the quotation request. A quotation is a purchase order in a 'Draft' state. Then the order has to be confirmed by the user, the state switch to 'Confirmed'. Then the supplier must confirm the order to change the state to 'Approved'. When the purchase order is paid and received, the state becomes 'Done'. If a cancel action occurs in the invoice or in the reception of goods, the state becomes in exception.", select=True),
2680- 'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
2681+ 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, states={'validated':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, help="The pricelist sets the currency used for this purchase order. It also computes the supplier price for the selected products/quantities."),
2682+ 'state': fields.function(_get_less_advanced_pol_state, string='Order State', method=True, type='selection', selection=PURCHASE_ORDER_STATE_SELECTION, readonly=True,
2683+ store = {
2684+ 'purchase.order.line': (_get_order, ['state'], 10),
2685+ },
2686+ select=True, help="The state of the purchase order or the quotation request. A quotation is a purchase order in a 'Draft' state. Then the order has to be confirmed by the user, the state switch to 'Confirmed'. Then the supplier must confirm the order to change the state to 'Approved'. When the purchase order is paid and received, the state becomes 'Done'. If a cancel action occurs in the invoice or in the reception of goods, the state becomes in exception."
2687+ ),
2688+
2689 'validator' : fields.many2one('res.users', 'Validated by', readonly=True),
2690 'notes': fields.text('Notes'),
2691- 'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', help="Invoices generated for a purchase order"),
2692 'picking_ids': fields.one2many('stock.picking', 'purchase_id', 'Picking List', readonly=True, help="This is the list of picking list that have been generated for this purchase"),
2693 'shipped':fields.boolean('Received', readonly=True, select=True, help="It indicates that a picking has been done"),
2694 'shipped_rate': fields.function(_shipped_rate, method=True, string='Received', type='float'),
2695- 'invoiced': fields.function(_invoiced, method=True, string='Invoiced & Paid', type='boolean', help="It indicates that an invoice has been paid"),
2696+ 'invoiced': fields.function(_invoiced, method=True, string='Invoiced', type='boolean', help="It indicates that an invoice has been generated"),
2697 'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
2698- 'invoice_method': fields.selection([('manual','Manual'),('order','From Order'),('picking','From Picking')], 'Invoicing Control', required=True,
2699- help="From Order: a draft invoice will be pre-generated based on the purchase order. The accountant " \
2700- "will just have to validate this invoice for control.\n" \
2701- "From Picking: a draft invoice will be pre-generated based on validated receptions.\n" \
2702- "Manual: allows you to generate suppliers invoices by chosing in the uninvoiced lines of all manual purchase orders."
2703- ),
2704 'minimum_planned_date':fields.function(_minimum_planned_date, fnct_inv=_set_minimum_planned_date, method=True, string='Expected Date', type='date', select=True, help="This is computed as the minimum scheduled date of all purchase order lines' products.",
2705 store = {
2706 'purchase.order.line': (_get_order, ['date_planned'], 10),
2707@@ -213,37 +583,188 @@
2708 'purchase.order.line': (_get_order, ['price_subtotal', 'taxes_id', 'price_unit', 'product_qty', 'product_id'], 10),
2709 }, multi="sums",help="The total amount"),
2710 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
2711- 'product_id': fields.related('order_line','product_id', type='many2one', relation='product.product', string='Product'),
2712 'create_uid': fields.many2one('res.users', 'Responsible'),
2713 'company_id': fields.many2one('res.company','Company',required=True,select=1),
2714 'stock_take_date': fields.date(string='Date of Stock Take', required=False),
2715+ 'fixed_order_type': fields.function(_is_fixed_type, method=True, type='char', size=256, string='Possible order types', store={
2716+ 'purchase.order': (lambda obj, cr, uid, ids, c={}: ids, ['order_line'], 10),
2717+ 'purchase.order.line': (_order_line_order_type, ['order_id'], 10),
2718+ },
2719+ ),
2720 }
2721 _defaults = {
2722+ 'po_confirmed': lambda *a: False,
2723+ 'order_type': lambda *a: 'regular',
2724+ 'priority': lambda *a: 'normal',
2725+ 'categ': lambda *a: 'other',
2726+ 'loan_duration': 2,
2727+ 'invoice_address_id': lambda obj, cr, uid, ctx: obj.pool.get('res.partner').address_get(cr, uid, obj.pool.get('res.users').browse(cr, uid, uid, ctx).company_id.partner_id.id, ['invoice'])['invoice'],
2728+ 'invoice_method': lambda *a: 'picking',
2729+ 'dest_address_id': lambda obj, cr, uid, ctx: obj.pool.get('res.partner').address_get(cr, uid, obj.pool.get('res.users').browse(cr, uid, uid, ctx).company_id.partner_id.id, ['delivery'])['delivery'],
2730+ 'no_line': lambda *a: True,
2731+ 'active': True,
2732+ 'name': lambda *a: False,
2733+ 'is_a_counterpart': False,
2734+ 'parent_order_name': False,
2735+ 'canceled_end': False,
2736+ 'split_po': False,
2737+ 'vat_ok': lambda obj, cr, uid, context: obj.pool.get('unifield.setup.configuration').get_config(cr, uid).vat_ok,
2738+ 'update_in_progress': False,
2739 'date_order': lambda *a: time.strftime('%Y-%m-%d'),
2740 'state': 'draft',
2741- 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
2742 'shipped': 0,
2743- 'invoice_method': 'order',
2744 'invoiced': 0,
2745 'partner_address_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').address_get(cr, uid, [context['partner_id']], ['default'])['default'],
2746 'pricelist_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').browse(cr, uid, context['partner_id']).property_product_pricelist_purchase.id,
2747 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'purchase.order', context=c),
2748+ 'fixed_order_type': lambda *a: json.dumps([]),
2749 }
2750 _sql_constraints = [
2751 ('name_uniq', 'unique(name)', 'Order Reference must be unique !'),
2752 ]
2753- _name = "purchase.order"
2754- _description = "Purchase Order"
2755- _order = "name desc"
2756+
2757+ def _check_po_from_fo(self, cr, uid, ids, context=None):
2758+ if not context:
2759+ context = {}
2760+ for po in self.browse(cr, uid, ids, context=context):
2761+ if po.partner_id.partner_type == 'internal' and po.po_from_fo:
2762+ return False
2763+ return True
2764+
2765+ def _check_order_type(self, cr, uid, ids, context=None):
2766+ '''
2767+ Check the integrity of the order type and the source order type
2768+ :param cr: Cursor to the database
2769+ :param uid: ID of the res.users that calls this method
2770+ :param ids: List of ID of sale.order to check
2771+ :param context: Context of the call
2772+ :return: True if the integrity is ok.
2773+ '''
2774+ err = []
2775+
2776+ order_types_dict = dict((x, y) for x, y in ORDER_TYPES_SELECTION)
2777+
2778+ for order in self.read(cr, uid, ids, ['name', 'fixed_order_type', 'order_type', 'is_a_counterpart'], context=context):
2779+ if order['is_a_counterpart'] and order['order_type'] != 'loan':
2780+ err.append(_('%s: This purchase order is a loan counterpart. You cannot change its order type') % order['name'])
2781+ else:
2782+ json_info = json.loads(order['fixed_order_type'])
2783+ if json_info and order['order_type'] not in json_info:
2784+ allowed_type = ' / '.join(order_types_dict.get(x) for x in json_info)
2785+ err.append(_('%s: Only %s order types are allowed for this purchase order') % (order['name'], allowed_type))
2786+
2787+ if err:
2788+ raise osv.except_osv(
2789+ _('Error'),
2790+ '\n'.join(x for x in err),
2791+ )
2792+
2793+ return True
2794+
2795+ _constraints = [
2796+ (_check_po_from_fo, 'You cannot choose an internal supplier for this purchase order', []),
2797+ (_check_order_type, 'The order type of the order is not consistent with the order type of the source', ['order_type'])
2798+ ]
2799+
2800+ def default_get(self, cr, uid, fields, context=None):
2801+ '''
2802+ Fill the unallocated_ok field according to Unifield setup
2803+ '''
2804+ res = super(purchase_order, self).default_get(cr, uid, fields, context=context)
2805+
2806+ setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
2807+ res.update({'unallocation_ok': False, 'allocation_setup': setup.allocation_setup})
2808+ if setup.allocation_setup == 'unallocated':
2809+ res.update({'unallocation_ok': True})
2810+
2811+ res.update({'name': False})
2812+
2813+ return res
2814+
2815+ def create(self, cr, uid, vals, context=None):
2816+ """
2817+ Filled in 'from_yml_test' to True if we come from tests
2818+ # UTP-114 demands purchase_list PO to be 'from picking'.
2819+ """
2820+ if not context:
2821+ context = {}
2822+
2823+ if vals.get('order_type'):
2824+ if vals.get('order_type') in ['donation_exp', 'donation_st']:
2825+ vals.update({'invoice_method': vals.get('partner_type', '') == 'section' and 'picking' or 'manual'})
2826+ elif vals.get('order_type') == 'loan':
2827+ vals.update({'invoice_method': 'manual'})
2828+ elif vals.get('order_type') in ['direct']:
2829+ vals.update({'invoice_method': 'order'})
2830+ if vals.get('partner_id'):
2831+ if self.pool.get('res.partner').read(cr, uid, vals.get('partner_id'), ['partner_type'], context=context)['partner_type'] == 'esc':
2832+ vals.update({'invoice_method': 'manual'})
2833+ else:
2834+ vals.update({'invoice_method': 'picking'})
2835+
2836+ if 'partner_id' in vals:
2837+ self._check_user_company(cr, uid, vals['partner_id'], context=context)
2838+ # we need to update the location_id because it is readonly and so does not pass in the vals of create and write
2839+ vals = self._get_location_id(cr, uid, vals, warehouse_id=vals.get('warehouse_id', False), context=context)
2840+ res = super(purchase_order, self).create(cr, uid, vals, context=context)
2841+ return res
2842+
2843+ def write(self, cr, uid, ids, vals, context=None):
2844+ '''
2845+ Check if the partner is correct.
2846+ # UTP-114 demand purchase_list PO to be "from picking" as invoice_method
2847+ '''
2848+ if not ids:
2849+ return True
2850+ if 'partner_id' in vals:
2851+ self._check_user_company(cr, uid, vals['partner_id'], context=context)
2852+
2853+
2854+ res_partner_obj = self.pool.get('res.partner')
2855+ for order in self.read(cr, uid, ids, ['partner_id', 'warehouse_id'], context=context):
2856+ partner_type = res_partner_obj.read(cr, uid, int(vals.get('partner_id', order['partner_id'][0])), ['partner_type'], context=context)['partner_type']
2857+ if vals.get('order_type'):
2858+ if vals.get('order_type') in ['donation_exp', 'donation_st']:
2859+ vals.update({'invoice_method': partner_type == 'section' and 'picking' or 'manual'})
2860+ elif vals.get('order_type') == 'loan':
2861+ vals.update({'invoice_method': 'manual'})
2862+ elif vals.get('order_type') in ['direct',] and partner_type != 'esc':
2863+ vals.update({'invoice_method': 'order'})
2864+ elif vals.get('order_type') in ['direct',] and partner_type == 'esc':
2865+ vals.update({'invoice_method': 'manual'})
2866+ else:
2867+ vals.update({'invoice_method': 'picking'})
2868+ # we need to update the location_id because it is readonly and so does not pass in the vals of create and write
2869+ vals = self._get_location_id(cr, uid, vals, warehouse_id=vals.get('warehouse_id', order['warehouse_id'] and order['warehouse_id'][0] or False), context=context)
2870+ # FIXME here it is useless to continue as the next loop will
2871+ # overwrite vals
2872+ break
2873+
2874+ # Fix bug invalid syntax for type date:
2875+ if 'valid_till' in vals and vals['valid_till'] == '':
2876+ vals['valid_till'] = False
2877+
2878+ res = super(purchase_order, self).write(cr, uid, ids, vals, context=context)
2879+
2880+ # Delete expected sale order line
2881+ if 'state' in vals and vals.get('state') not in ('draft', 'validated'):
2882+ exp_sol_ids = self.pool.get('expected.sale.order.line').search(cr,
2883+ uid, [('po_id', 'in', ids)], order='NO_ORDER', context=context)
2884+ self.pool.get('expected.sale.order.line').unlink(cr, uid, exp_sol_ids, context=context)
2885+
2886+ return res
2887
2888 def unlink(self, cr, uid, ids, context=None):
2889+ if self.get_so_ids_from_po_ids(cr, uid, ids, context=context):
2890+ raise osv.except_osv(_('Error'), _('You cannot remove a Purchase order that is linked to a Field Order or an Internal Request. Please cancel it instead.'))
2891+
2892 purchase_orders = self.read(cr, uid, ids, ['state'], context=context)
2893 unlink_ids = []
2894 for s in purchase_orders:
2895 if s['state'] in ['draft','cancel']:
2896 unlink_ids.append(s['id'])
2897 else:
2898- raise osv.except_osv(_('Invalid action !'), _('Cannot delete Purchase Order(s) which are in %s State!') % _(dict(purchase_order.STATE_SELECTION).get(s['state'])))
2899+ raise osv.except_osv(_('Invalid action !'), _('Cannot delete Purchase Order(s) which are in %s State!') % _(dict(PURCHASE_ORDER_STATE_SELECTION).get(s['state'])))
2900
2901 # TODO: temporary fix in 5.0, to remove in 5.2 when subflows support
2902 # automatically sending subflow.delete upon deletion
2903@@ -257,105 +778,45 @@
2904
2905 return super(purchase_order, self).unlink(cr, uid, unlink_ids, context=context)
2906
2907- def button_dummy(self, cr, uid, ids, context=None):
2908- return True
2909-
2910- def onchange_dest_address_id(self, cr, uid, ids, adr_id):
2911- if not adr_id:
2912- return {}
2913- values = {'warehouse_id': False}
2914- part_id = self.pool.get('res.partner.address').browse(cr, uid, adr_id).partner_id
2915- if part_id:
2916- loc_id = part_id.property_stock_customer.id
2917- values.update({'location_id': loc_id})
2918- return {'value':values}
2919-
2920- def onchange_warehouse_id(self, cr, uid, ids, warehouse_id):
2921- if not warehouse_id:
2922- return {}
2923- res = self.pool.get('stock.warehouse').read(cr, uid, [warehouse_id], ['lot_input_id'])[0]['lot_input_id'][0]
2924- return {'value':{'location_id': res, 'dest_address_id': False}}
2925-
2926- def onchange_partner_id(self, cr, uid, ids, part):
2927-
2928- if not part:
2929- return {'value':{'partner_address_id': False, 'fiscal_position': False}}
2930- addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['default'])
2931- part = self.pool.get('res.partner').browse(cr, uid, part)
2932- pricelist = part.property_product_pricelist_purchase.id
2933- fiscal_position = part.property_account_position and part.property_account_position.id or False
2934- return {'value':{'partner_address_id': addr['default'], 'pricelist_id': pricelist, 'fiscal_position': fiscal_position}}
2935-
2936- def wkf_approve_order(self, cr, uid, ids, context=None):
2937- self.write(cr, uid, ids, {'state': 'approved', 'date_approve': time.strftime('%Y-%m-%d')})
2938- return True
2939-
2940- def _hook_confirm_order_message(self, cr, uid, context=None, *args, **kwargs):
2941- '''
2942- Add a hook to modify the logged message
2943- '''
2944- # Some verifications
2945- if context is None:
2946- context = {}
2947-
2948- return kwargs['message']
2949-
2950- def _hook_confirm_order_update_corresponding_so(self, cr, uid, ids, context=None, *args, **kwargs):
2951- '''
2952- Please copy this to your module's method also.
2953- This hook belongs to the wkf_confirm_order method from purchase>purchase.py>purchase_order
2954-
2955- - allow to execute code in order to update corresponding sale order lines and sale orders if exist
2956- '''
2957- # Some verifications
2958- if context is None:
2959- context = {}
2960- if isinstance(ids, (int, long)):
2961- ids = [ids]
2962-
2963- return True
2964-
2965- #UTP-872: extracted this method for a particularly check with sync
2966- def _hook_check_po_no_line(self, po, context):
2967- if not po.order_line:
2968- raise osv.except_osv(_('Error !'), _('You can not confirm purchase order without Purchase Order Lines.'))
2969-
2970- def wkf_confirm_order(self, cr, uid, ids, context=None):
2971- todo = []
2972- for po in self.browse(cr, uid, ids, context=context):
2973- self._hook_check_po_no_line(po, context)
2974- for line in po.order_line:
2975- if line.state=='draft':
2976- todo.append(line.id)
2977- message = _("Purchase order '%s' is confirmed.") % (po.name,)
2978- message = self._hook_confirm_order_message(cr, uid, context=context, message=message, po=po)
2979- self.log(cr, uid, po.id, message)
2980- # hook for corresponding Fo update
2981- self._hook_confirm_order_update_corresponding_so(cr, uid, ids, context=context, po=po)
2982-# current_name = self.name_get(cr, uid, ids)[0][1]
2983- self.pool.get('purchase.order.line').action_confirm(cr, uid, todo, context)
2984- for id in ids:
2985- self.write(cr, uid, [id], {'state' : 'confirmed', 'validator' : uid})
2986- return True
2987-
2988- def wkf_warn_buyer(self, cr, uid, ids):
2989- self.write(cr, uid, ids, {'state' : 'wait', 'validator' : uid})
2990- request = pooler.get_pool(cr.dbname).get('res.request')
2991- for po in self.browse(cr, uid, ids):
2992- managers = []
2993- for oline in po.order_line:
2994- manager = oline.product_id.product_manager
2995- if manager and not (manager.id in managers):
2996- managers.append(manager.id)
2997- for manager_id in managers:
2998- request.create(cr, uid,{
2999- 'name' : _("Purchase amount over the limit"),
3000- 'act_from' : uid,
3001- 'act_to' : manager_id,
3002- 'body': _('Somebody has just confirmed a purchase with an amount over the defined limit'),
3003- 'ref_partner_id': po.partner_id.id,
3004- 'ref_doc1': 'purchase.order,%d' % (po.id,),
3005- })
3006+ def _hook_copy_name(self, cr, uid, ids, context=None, *args, **kwargs):
3007+ '''
3008+ HOOK from purchase>purchase.py for COPY function. Modification of default copy values
3009+ define which name value will be used
3010+ '''
3011+ return {'state':'draft',
3012+ 'shipped':False,
3013+ 'invoiced':False,
3014+ 'invoice_ids': [],
3015+ 'picking_ids': [],
3016+ 'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
3017+ }
3018+
3019+
3020+ def copy(self, cr, uid, p_id, default=None, context=None):
3021+ '''
3022+ Remove loan_id field on new purchase.order
3023+ '''
3024+ if not default:
3025+ default = {}
3026+ if context is None:
3027+ context = {}
3028+
3029+ update_values = self._hook_copy_name(cr, uid, [p_id], context=context, default=default)
3030+ default.update(update_values)
3031+ # if the copy comes from the button duplicate
3032+ if context.get('from_button'):
3033+ default.update({'is_a_counterpart': False})
3034+ default.update({'loan_id': False, 'merged_line_ids': False, 'partner_ref': False, 'po_confirmed': False})
3035+ if not context.get('keepOrigin', False):
3036+ default.update({'origin': False})
3037+
3038+ if not 'date_confirm' in default:
3039+ default['date_confirm'] = False
3040+ if not default.get('related_sourcing_id', False):
3041+ default['related_sourcing_id'] = False
3042+
3043+ return super(purchase_order, self).copy(cr, uid, p_id, default, context=context)
3044+
3045 def inv_line_create(self, cr, uid, a, ol):
3046 return (0, False, {
3047 'name': ol.name,
3048@@ -368,20 +829,6 @@
3049 'account_analytic_id': ol.account_analytic_id.id or False,
3050 })
3051
3052- def action_cancel_draft(self, cr, uid, ids, *args):
3053- if not len(ids):
3054- return False
3055- self.write(cr, uid, ids, {'state':'draft','shipped':0})
3056- wf_service = netsvc.LocalService("workflow")
3057- for p_id in ids:
3058- # Deleting the existing instance of workflow for PO
3059- wf_service.trg_delete(uid, 'purchase.order', p_id, cr)
3060- wf_service.trg_create(uid, 'purchase.order', p_id, cr)
3061- for (id,name) in self.name_get(cr, uid, ids):
3062- message = _("Purchase order '%s' has been set in draft state.") % name
3063- self.log(cr, uid, id, message)
3064- return True
3065-
3066 def action_invoice_create(self, cr, uid, ids, *args):
3067 res = False
3068
3069@@ -425,6 +872,15 @@
3070 'payment_term': o.partner_id.property_payment_term and o.partner_id.property_payment_term.id or False,
3071 'company_id': o.company_id.id,
3072 }
3073+ if o.order_type == 'purchase_list':
3074+ inv['purchase_list'] = 1
3075+ elif o.order_type == 'in_kind':
3076+ inkind_journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'inkind'), ('is_current_instance', '=', True)])
3077+ if not inkind_journal_ids:
3078+ raise osv.except_osv(_('Error'), _('No In-kind Donation journal found!'))
3079+ inv['journal_id'] = inkind_journal_ids[0]
3080+ inv['is_inkind_donation'] = True
3081+
3082 inv_id = self.pool.get('account.invoice').create(cr, uid, inv, {'type':'in_invoice', 'journal_type': 'purchase'})
3083 self.pool.get('account.invoice').button_compute(cr, uid, [inv_id], {'type':'in_invoice'}, set_total=True)
3084 self.pool.get('purchase.order.line').write(cr, uid, todo, {'invoiced':True})
3085@@ -432,120 +888,78 @@
3086 res = inv_id
3087 return res
3088
3089- def has_stockable_product(self,cr, uid, ids, *args):
3090- for order in self.browse(cr, uid, ids):
3091- for order_line in order.order_line:
3092- if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
3093- return True
3094- return False
3095-
3096- def action_cancel(self, cr, uid, ids, context=None):
3097- for purchase in self.browse(cr, uid, ids, context=context):
3098- for pick in purchase.picking_ids:
3099- if pick.state not in ('draft','cancel'):
3100- raise osv.except_osv(
3101- _('Could not cancel purchase order !'),
3102- _('You must first cancel all picking attached to this purchase order.'))
3103- for pick in purchase.picking_ids:
3104- wf_service = netsvc.LocalService("workflow")
3105- wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_cancel', cr)
3106- for inv in purchase.invoice_ids:
3107- if inv and inv.state not in ('cancel','draft'):
3108- raise osv.except_osv(
3109- _('Could not cancel this purchase order !'),
3110- _('You must first cancel all invoices attached to this purchase order.'))
3111- if inv:
3112- wf_service = netsvc.LocalService("workflow")
3113- wf_service.trg_validate(uid, 'account.invoice', inv.id, 'invoice_cancel', cr)
3114- self.write(cr,uid,ids,{'state':'cancel'})
3115- for (id,name) in self.name_get(cr, uid, ids):
3116- message = _("Purchase order '%s' is cancelled.") % name
3117- self.log(cr, uid, id, message)
3118+ def button_dummy(self, cr, uid, ids, context=None):
3119 return True
3120
3121- def action_picking_create(self,cr, uid, ids, *args):
3122- picking_id = False
3123- for order in self.browse(cr, uid, ids):
3124- loc_id = order.partner_id.property_stock_supplier.id
3125- istate = 'none'
3126- if order.invoice_method=='picking':
3127- istate = '2binvoiced'
3128- pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.in')
3129- picking_id = self.pool.get('stock.picking').create(cr, uid, {
3130- 'name': pick_name,
3131- 'origin': order.name+((order.origin and (':'+order.origin)) or ''),
3132- 'type': 'in',
3133- 'address_id': order.dest_address_id.id or order.partner_address_id.id,
3134- 'invoice_state': istate,
3135- 'purchase_id': order.id,
3136- 'company_id': order.company_id.id,
3137- 'move_lines' : [],
3138- })
3139- todo_moves = []
3140- for order_line in order.order_line:
3141- if not order_line.product_id:
3142- continue
3143- if order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
3144- dest = order.location_id.id
3145- move = self.pool.get('stock.move').create(cr, uid, {
3146- 'name': order.name + ': ' +(order_line.name or ''),
3147- 'product_id': order_line.product_id.id,
3148- 'product_qty': order_line.product_qty,
3149- 'product_uos_qty': order_line.product_qty,
3150- 'product_uom': order_line.product_uom.id,
3151- 'product_uos': order_line.product_uom.id,
3152- 'date': order_line.date_planned,
3153- 'date_expected': order_line.date_planned,
3154- 'location_id': loc_id,
3155- 'location_dest_id': dest,
3156- 'picking_id': picking_id,
3157- 'move_dest_id': order_line.move_dest_id.id,
3158- 'state': 'draft',
3159- 'purchase_line_id': order_line.id,
3160- 'company_id': order.company_id.id,
3161- 'price_unit': order_line.price_unit
3162- })
3163- if order_line.move_dest_id:
3164- self.pool.get('stock.move').write(cr, uid, [order_line.move_dest_id.id], {'location_id':order.location_id.id})
3165- todo_moves.append(move)
3166- self.pool.get('stock.move').action_confirm(cr, uid, todo_moves)
3167- self.pool.get('stock.move').force_assign(cr, uid, todo_moves)
3168- wf_service = netsvc.LocalService("workflow")
3169- wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
3170- raise
3171- return picking_id
3172-
3173- def _hook_copy_name(self, cr, uid, ids, context=None, *args, **kwargs):
3174- '''
3175- HOOK from purchase>purchase.py for COPY function. Modification of default copy values
3176- define which name value will be used
3177- '''
3178- return {'state':'draft',
3179- 'shipped':False,
3180- 'invoiced':False,
3181- 'invoice_ids': [],
3182- 'picking_ids': [],
3183- 'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
3184- }
3185-
3186- def copy(self, cr, uid, id, default=None, context=None):
3187- if not default:
3188- default = {}
3189- update_values = self._hook_copy_name(cr, uid, [id], context=context, default=default)
3190- default.update(update_values)
3191- return super(purchase_order, self).copy(cr, uid, id, default, context)
3192-
3193 def _hook_o_line_value(self, cr, uid, *args, **kwargs):
3194- '''
3195- Hook to change the values of the PO line
3196- '''
3197- return kwargs['o_line']
3198+ o_line = super(purchase_order, self)._hook_o_line_value(cr, uid, *args, **kwargs)
3199+ order_line = kwargs['order_line']
3200+
3201+ # Copy all fields except order_id and analytic_distribution_id
3202+ fields = ['product_uom', 'price_unit', 'move_dest_id', 'product_qty', 'partner_id',
3203+ 'confirmed_delivery_date', 'nomenclature_description', 'default_code',
3204+ 'nomen_manda_0', 'nomen_manda_1', 'nomen_manda_2', 'nomen_manda_3',
3205+ 'nomenclature_code', 'name', 'default_name', 'comment', 'date_planned',
3206+ 'to_correct_ok', 'text_error', 'select_fo', 'project_ref', 'external_ref',
3207+ 'nomen_sub_0', 'nomen_sub_1', 'nomen_sub_2', 'nomen_sub_3', 'nomen_sub_4',
3208+ 'nomen_sub_5', 'linked_sol_id', 'change_price_manually', 'old_price_unit',
3209+ 'origin', 'account_analytic_id', 'product_id', 'company_id', 'notes', 'taxes_id',
3210+ 'link_so_id', 'from_fo', 'sale_order_line_id', 'tender_line_id', 'dest_partner_id']
3211+
3212+ for field in fields:
3213+ field_val = getattr(order_line, field)
3214+ if isinstance(field_val, browse_record):
3215+ field_val = field_val.id
3216+ elif isinstance(field_val, browse_null):
3217+ field_val = False
3218+ elif isinstance(field_val, list):
3219+ field_val = ((6, 0, tuple([v.id for v in field_val])),)
3220+ o_line[field] = field_val
3221+
3222+
3223+ # Set the analytic distribution
3224+ distrib_id = False
3225+ if order_line.analytic_distribution_id:
3226+ distrib_id = self.pool.get('analytic.distribution').copy(cr, uid, order_line.analytic_distribution_id.id)
3227+ elif order_line.order_id.analytic_distribution_id:
3228+ distrib_id = self.pool.get('analytic.distribution').copy(cr, uid, order_line.order_id.analytic_distribution_id.id)
3229+
3230+ o_line['analytic_distribution_id'] = distrib_id
3231+
3232+ return o_line
3233+
3234
3235 def _hook_order_infos(self, cr, uid, *args, **kwargs):
3236 '''
3237 Hook to change the values of the PO
3238 '''
3239- return kwargs['order_infos']
3240+ order_infos = super(purchase_order, self)._hook_order_infos(cr, uid, *args, **kwargs)
3241+ order_id = kwargs['order_id']
3242+
3243+ fields = ['invoice_method', 'minimum_planned_date', 'order_type',
3244+ 'categ', 'priority', 'internal_type', 'arrival_date',
3245+ 'transport_type', 'shipment_date', 'ready_to_ship_date',
3246+ 'cross_docking_ok', 'delivery_confirmed_date',
3247+ 'est_transport_lead_time', 'transport_mode', 'location_id',
3248+ 'dest_address_id', 'incoterm_id']
3249+
3250+
3251+ delivery_requested_date = getattr(order_id, 'delivery_requested_date')
3252+ if not order_infos.get('delivery_requested_date') or delivery_requested_date < order_infos['delivery_requested_date']:
3253+ order_infos['delivery_requested_date'] = delivery_requested_date
3254+
3255+
3256+ for field in fields:
3257+ field_val = getattr(order_id, field)
3258+ if isinstance(field_val, browse_record):
3259+ field_val = field_val.id
3260+ elif isinstance(field_val, browse_null):
3261+ field_val = False
3262+ elif isinstance(field_val, list):
3263+ field_val = ((6, 0, tuple([v.id for v in field_val])),)
3264+ order_infos[field] = field_val
3265+
3266+ return order_infos
3267
3268 def do_merge(self, cr, uid, ids, context=None):
3269 """
3270@@ -633,11 +1047,11 @@
3271 o_line[field] = field_val
3272 o_line['uom_factor'] = order_line.product_uom and order_line.product_uom.factor or 1.0
3273 o_line = self._hook_o_line_value(cr, uid, o_line=o_line, order_line=order_line)
3274- if order_line.procurement_id:
3275+ if order_line.linked_sol_id:
3276 no_proc_ids.append(order_line.id)
3277
3278 if no_proc_ids:
3279- line_obj.write(cr, uid, no_proc_ids, {'procurement_id': False}, context=context)
3280+ line_obj.write(cr, uid, no_proc_ids, {'linked_sol_id': False}, context=context)
3281
3282 allorders = []
3283 orders_info = {}
3284@@ -664,336 +1078,916 @@
3285 wf_service.trg_validate(uid, 'purchase.order', old_id, 'purchase_cancel', cr)
3286 return orders_info
3287
3288+ def purchase_cancel(self, cr, uid, ids, context=None):
3289+ '''
3290+ Call the wizard to ask if you want to re-source the line
3291+ '''
3292+ line_obj = self.pool.get('purchase.order.line')
3293+ wiz_obj = self.pool.get('purchase.order.cancel.wizard')
3294+ exp_sol_obj = self.pool.get('expected.sale.order.line')
3295+ so_obj = self.pool.get('sale.order')
3296+ data_obj = self.pool.get('ir.model.data')
3297+ wf_service = netsvc.LocalService("workflow")
3298+
3299+ if context is None:
3300+ context = {}
3301+
3302+ if isinstance(ids, (int, long)):
3303+ ids = [ids]
3304+
3305+ if context.get('rfq_ok', False):
3306+ view_id = data_obj.get_object_reference(cr, uid, 'tender_flow', 'rfq_cancel_wizard_form_view')[1]
3307+ else:
3308+ view_id = data_obj.get_object_reference(cr, uid, 'purchase_override', 'purchase_order_cancel_wizard_form_view')[1]
3309+
3310+ for po in self.browse(cr, uid, ids, context=context):
3311+ for pol in po.order_line:
3312+ wiz_id = wiz_obj.create(cr, uid, {'order_id': po.id}, context=context)
3313+ return {
3314+ 'type': 'ir.actions.act_window',
3315+ 'res_model': 'purchase.order.cancel.wizard',
3316+ 'res_id': wiz_id,
3317+ 'view_type': 'form',
3318+ 'view_mode': 'form',
3319+ 'view_id': [view_id],
3320+ 'target': 'new',
3321+ 'context': context
3322+ }
3323+
3324+ return True
3325+
3326+ def _check_restriction_line(self, cr, uid, ids, context=None):
3327+ '''
3328+ Check restriction on products
3329+ '''
3330+ if isinstance(ids, (int, long)):
3331+ ids = [ids]
3332+
3333+ line_obj = self.pool.get('purchase.order.line')
3334+ res = True
3335+
3336+ for order in self.read(cr, uid, ids, ['order_line'], context=context):
3337+ res = res and line_obj._check_restriction_line(cr, uid, order['order_line'], context=context)
3338+
3339+ return res
3340+
3341+ def _check_user_company(self, cr, uid, company_id, context=None):
3342+ '''
3343+ Remove the possibility to make a PO to user's company
3344+ '''
3345+ user_company_id = self.pool.get('res.users').read(cr, uid, uid, ['company_id'], context=context)['company_id'][0]
3346+ if company_id == user_company_id:
3347+ raise osv.except_osv(_('Error'), _('You cannot made a purchase order to your own company !'))
3348+
3349+ return True
3350+
3351+
3352+ def onchange_internal_type(self, cr, uid, ids, order_type, partner_id, categ, dest_partner_id=False, warehouse_id=False, delivery_requested_date=False):
3353+ '''
3354+ Changes the invoice method of the purchase order according to
3355+ the choosen order type
3356+ Changes the partner to local market if the type is Purchase List
3357+ '''
3358+ partner_obj = self.pool.get('res.partner')
3359+ v = {}
3360+ # the domain on the onchange was replace by a several fields.function that you can retrieve in the
3361+ # file msf_custom_settings/view/purchase_view.xml: domain="[('supplier', '=', True), ('id', '!=', company_id), ('check_partner_po', '=', order_type), ('check_partner_rfq', '=', tender_id)]"
3362+ w = {}
3363+ local_market = None
3364+ partner = partner_id and partner_obj.read(cr, uid, partner_id, ['partner_type']) or False
3365+
3366+ if ids:
3367+ order_types_dict = dict((x, y) for x, y in ORDER_TYPES_SELECTION)
3368+ err = []
3369+ for order in self.read(cr, uid, ids, ['name', 'fixed_order_type', 'order_type', 'is_a_counterpart']):
3370+ if order['is_a_counterpart'] and order_type != 'loan':
3371+ err.append(_('%s: This purchase order is a loan counterpart. You cannot change its order type') % order['name'])
3372+ else:
3373+ json_info = json.loads(order['fixed_order_type'])
3374+ if json_info and order_type not in json_info:
3375+ allowed_type = ' / '.join(order_types_dict.get(x) for x in json_info)
3376+ err.append(
3377+ _('%s: Only %s order types are allowed for this purchase order') % (order['name'], allowed_type))
3378+
3379+ if err:
3380+ return {
3381+ 'warning': {
3382+ 'title': _('Error'),
3383+ 'message': '\n'.join(x for x in err),
3384+ },
3385+ 'value': {
3386+ 'order_type': order['order_type'],
3387+ }
3388+ }
3389+
3390+ # check if the current PO was created from scratch :
3391+ if order_type == 'direct':
3392+ if not self.pool.get('purchase.order.line').search_exist(cr, uid, [('order_id', 'in', ids), ('linked_sol_id', '=', False)]):
3393+ order_type_value = self.read(cr, uid, ids, ['order_type'])
3394+ order_type_value = order_type_value[0].get('order_type', 'regular') if order_type_value else 'regular'
3395+ return {
3396+ 'value': {'order_type': order_type_value},
3397+ 'warning': {
3398+ 'title': _('Error'),
3399+ 'message': _('You cannot create a direct purchase order from scratch')
3400+ },
3401+ }
3402+
3403+ # Search the local market partner id
3404+ data_obj = self.pool.get('ir.model.data')
3405+ data_id = data_obj.search(cr, uid,
3406+ [('module', '=', 'order_types'),
3407+ ('model', '=', 'res.partner'),
3408+ ('name', '=', 'res_partner_local_market')],
3409+ limit=1, order='NO_ORDER')
3410+ if data_id:
3411+ local_market = data_obj.read(cr, uid, data_id, ['res_id'])[0]['res_id']
3412+
3413+ if order_type == 'loan':
3414+ setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
3415+
3416+ if not setup.field_orders_ok:
3417+ return {'value': {'order_type': 'regular'},
3418+ 'warning': {'title': 'Error',
3419+ 'message': 'The Field orders feature is not activated on your system, so, you cannot create a Loan Purchase Order !'}}
3420+
3421+ if order_type in ['donation_exp', 'donation_st']:
3422+ v['invoice_method'] = partner and partner['partner_type'] == 'section' and 'picking' or 'manual'
3423+ elif order_type == 'loan':
3424+ v['invoice_method'] = 'manual'
3425+ elif order_type in ['direct']:
3426+ v['invoice_method'] = 'order'
3427+ elif order_type in ['in_kind', 'purchase_list']:
3428+ v['invoice_method'] = 'picking'
3429+ else:
3430+ v['invoice_method'] = 'picking'
3431+
3432+ company_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.partner_id.id
3433+
3434+ if order_type == 'direct' and dest_partner_id and dest_partner_id != company_id:
3435+ cp_address_id = self.pool.get('res.partner').address_get(cr, uid, dest_partner_id, ['delivery'])['delivery']
3436+ v.update({'dest_address_id': cp_address_id})
3437+ elif order_type == 'direct':
3438+ v.update({'dest_address_id': False, 'dest_partner_id': False})
3439+ else:
3440+ cp_address_id = self.pool.get('res.partner').address_get(cr, uid, company_id, ['delivery'])['delivery']
3441+ v.update({'dest_address_id': cp_address_id, 'dest_partner_id': company_id})
3442+
3443+ if partner and partner_id != local_market:
3444+ if partner['partner_type'] in ('internal', 'esc') and order_type in ('regular', 'direct'):
3445+ v['invoice_method'] = 'manual'
3446+ elif partner['partner_type'] not in ('external', 'esc') and order_type == 'direct':
3447+ v.update({'partner_address_id': False, 'partner_id': False, 'pricelist_id': False,})
3448+ w.update({'message': 'You cannot have a Direct Purchase Order with a partner which is not external or an ESC',
3449+ 'title': 'An error has occurred !'})
3450+ elif partner_id and partner_id == local_market and order_type != 'purchase_list':
3451+ v['partner_id'] = None
3452+ v['partner_address_id'] = None
3453+ v['pricelist_id'] = None
3454+
3455+ if order_type == 'purchase_list':
3456+ if local_market:
3457+ partner = self.pool.get('res.partner').browse(cr, uid, local_market)
3458+ v['partner_id'] = partner.id
3459+ if partner.address:
3460+ v['partner_address_id'] = partner.address[0].id
3461+ if partner.property_product_pricelist_purchase:
3462+ v['pricelist_id'] = partner.property_product_pricelist_purchase.id
3463+ elif order_type == 'direct':
3464+ v['cross_docking_ok'] = False
3465+
3466+ return {'value': v, 'warning': w}
3467+
3468+ def onchange_partner_id(self, cr, uid, ids, part, *a, **b):
3469+ '''
3470+ Fills the Requested and Confirmed delivery dates
3471+ '''
3472+ if isinstance(ids, (int, long)):
3473+ ids = [ids]
3474+
3475+ if not part:
3476+ return {'value':{'partner_address_id': False, 'fiscal_position': False}}
3477+
3478+ addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['default'])
3479+ part = self.pool.get('res.partner').browse(cr, uid, part)
3480+ pricelist = part.property_product_pricelist_purchase.id
3481+ fiscal_position = part.property_account_position and part.property_account_position.id or False
3482+ res = {'value':{'partner_address_id': addr['default'], 'pricelist_id': pricelist, 'fiscal_position': fiscal_position}}
3483+
3484+
3485+ partner_obj = self.pool.get('res.partner')
3486+ product_obj = self.pool.get('product.product')
3487+ partner = partner_obj.read(cr, uid, part.id, ['partner_type'])
3488+ if ids:
3489+ # Check the restrction of product in lines
3490+ for order in self.browse(cr, uid, ids):
3491+ for line in order.order_line:
3492+ if line.product_id:
3493+ res, test = product_obj._on_change_restriction_error(cr, uid, line.product_id.id, field_name='partner_id', values=res, vals={'partner_id': part})
3494+ if test:
3495+ res.setdefault('value', {}).update({'partner_address_id': False})
3496+ return res
3497+ if partner['partner_type'] in ('internal', 'esc'):
3498+ res['value']['invoice_method'] = 'manual'
3499+ elif ids and partner['partner_type'] == 'intermission':
3500+ try:
3501+ intermission = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
3502+ 'analytic_account_project_intermission')[1]
3503+ except ValueError:
3504+ intermission = 0
3505+ cr.execute('''select po.id from purchase_order po
3506+ left join purchase_order_line pol on pol.order_id = po.id
3507+ left join cost_center_distribution_line cl1 on cl1.distribution_id = po.analytic_distribution_id
3508+ left join cost_center_distribution_line cl2 on cl2.distribution_id = pol.analytic_distribution_id
3509+ where po.id in %s and (cl1.analytic_id!=%s or cl2.analytic_id!=%s)''', (tuple(ids), intermission, intermission))
3510+ if cr.rowcount > 0:
3511+ res.setdefault('warning', {})
3512+ msg = _('You set an intermission partner, at validation Cost Centers will be changed to intermission.')
3513+ if res.get('warning', {}).get('message'):
3514+ res['warning']['message'] += msg
3515+ else:
3516+ res['warning'] = {'title': _('Warning'), 'message': msg}
3517+ return res
3518+
3519+ def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, order_type, dest_address_id):
3520+ '''
3521+ Change the destination address to the destination address of the company if False
3522+ '''
3523+ res = {}
3524+ if not warehouse_id:
3525+ wh_info = self.pool.get('stock.warehouse').read(cr, uid, [warehouse_id], ['lot_input_id'])
3526+ res = {'value':{'location_id': wh_info[0]['lot_input_id'][0], 'dest_address_id': False, 'cross_docking_ok': False}}
3527+
3528+ if not res.get('value', {}).get('dest_address_id') and order_type!='direct':
3529+ cp_address_id = self.pool.get('res.partner').address_get(cr, uid, self.pool.get('res.users').browse(cr, uid, uid).company_id.partner_id.id, ['delivery'])['delivery']
3530+ if 'value' in res:
3531+ res['value'].update({'dest_address_id': cp_address_id})
3532+ else:
3533+ res.update({'value': {'dest_address_id': cp_address_id}})
3534+ if order_type == 'direct' or dest_address_id:
3535+ if 'dest_address_id' in res.get('value', {}):
3536+ res['value'].pop('dest_address_id')
3537+
3538+ return res
3539+
3540+ def on_change_dest_partner_id(self, cr, uid, ids, dest_partner_id, context=None):
3541+ '''
3542+ Fill automatically the destination address according to the destination partner
3543+ '''
3544+ v = {}
3545+
3546+ if not context:
3547+ context = {}
3548+
3549+ if not dest_partner_id:
3550+ v.update({'dest_address_id': False})
3551+ else:
3552+ delivery_addr = self.pool.get('res.partner').address_get(cr, uid, dest_partner_id, ['delivery'])
3553+ v.update({'dest_address_id': delivery_addr['delivery']})
3554+ return {'value': v}
3555+
3556+ def change_currency(self, cr, uid, ids, context=None):
3557+ '''
3558+ Launches the wizard to change the currency and update lines
3559+ '''
3560+ if not context:
3561+ context = {}
3562+
3563+ if isinstance(ids, (int, long)):
3564+ ids = [ids]
3565+
3566+ for order in self.browse(cr, uid, ids, context=context):
3567+ data = {'order_id': order.id,
3568+ 'partner_id': order.partner_id.id,
3569+ 'partner_type': order.partner_id.partner_type,
3570+ 'new_pricelist_id': order.pricelist_id.id,
3571+ 'currency_rate': 1.00,
3572+ 'old_pricelist_id': order.pricelist_id.id}
3573+ wiz = self.pool.get('purchase.order.change.currency').create(cr, uid, data, context=context)
3574+ return {'type': 'ir.actions.act_window',
3575+ 'res_model': 'purchase.order.change.currency',
3576+ 'view_type': 'form',
3577+ 'view_mode': 'form',
3578+ 'res_id': wiz,
3579+ 'target': 'new'}
3580+
3581+ return True
3582+
3583+ def order_line_change(self, cr, uid, ids, order_line):
3584+ res = {'no_line': True}
3585+
3586+ if order_line:
3587+ res = {'no_line': False}
3588+
3589+ return {'value': res}
3590+
3591+ def _get_destination_ok(self, cr, uid, lines, context):
3592+ dest_ok = False
3593+ for line in lines:
3594+ is_inkind = line.order_id and line.order_id.order_type == 'in_kind' or False
3595+ dest_ok = line.account_4_distribution and line.account_4_distribution.destination_ids or False
3596+ if not dest_ok:
3597+ if is_inkind:
3598+ raise osv.except_osv(_('Error'), _('No destination found. An In-kind Donation account is probably missing for this line: %s.') % (line.name or ''))
3599+ raise osv.except_osv(_('Error'), _('No destination found for this line: %s.') % (line.name or '',))
3600+ return dest_ok
3601+
3602+ def check_analytic_distribution(self, cr, uid, ids, context=None, create_missing=False):
3603+ """
3604+ Check analytic distribution validity for given PO.
3605+ Also check that partner have a donation account (is PO is in_kind)
3606+ """
3607+ # Objects
3608+ pol_obj = self.pool.get('purchase.order.line')
3609+
3610+ if context is None:
3611+ context = {}
3612+
3613+ if isinstance(ids, (int, long)):
3614+ ids = [ids]
3615+
3616+ po_line_ids = pol_obj.search(cr, uid, [('oder_id', 'in', id), ('state', '!=', 'cancelled')], context=context)
3617+ if po_line_ids:
3618+ return pol_obj.check_analytic_distribution(cr, uid, po_line_ids, context=context, create_missing=create_missing)
3619+ return True
3620+
3621+ def check_if_stock_take_date_with_esc_partner(self, cr, uid, ids, context=None):
3622+ """
3623+ Check if the PO and all lines have a date of stock take with an ESC Partner
3624+ """
3625+ if isinstance(ids, (int, long)):
3626+ ids = [ids]
3627+
3628+ for po in self.browse(cr, uid, ids, context=context):
3629+ if po.partner_type == 'esc':
3630+ if not po.stock_take_date and self.pool.get('purchase.order.line').search_exist(cr, uid, [('order_id', '=', po.id), ('stock_take_date', '=', False)], context=context):
3631+ raise osv.except_osv(_('Warning !'), _(
3632+ 'The Date of Stock Take is required for a Purchase Order if the Partner is an ESC.'))
3633+ if self.pool.get('purchase.order.line').search_exist(cr, uid, [('order_id', '=', po.id), ('stock_take_date', '=', False)], context=context):
3634+ raise osv.except_osv(_('Warning !'), _('The Date of Stock Take is required for all Purchase Order lines if the Partner is an ESC.'))
3635+ return True
3636+
3637+ def get_so_ids_from_po_ids(self, cr, uid, ids, context=None):
3638+ '''
3639+ receive the list of purchase order ids
3640+ return the list of sale order ids corresponding
3641+ '''
3642+ if not context:
3643+ context = {}
3644+ if isinstance(ids, (int, long)):
3645+ ids = [ids]
3646+
3647+ so_ids = set()
3648+ for po in self.browse(cr, uid, ids, context=context):
3649+ for pol in po.order_line:
3650+ if pol.linked_sol_id:
3651+ so_ids.add(pol.linked_sol_id.order_id.id)
3652+
3653+ return list(so_ids)
3654+
3655+ def get_sol_ids_from_po_ids(self, cr, uid, ids, context=None):
3656+ '''
3657+ receive the list of purchase order ids
3658+ return the list of sale order line ids corresponding
3659+ '''
3660+ if not context:
3661+ context = {}
3662+ if isinstance(ids, (int, long)):
3663+ ids = [ids]
3664+
3665+ sol_ids = set()
3666+ for po in self.browse(cr, uid, ids, context=context):
3667+ for pol in po.order_line:
3668+ if pol.linked_sol_id:
3669+ sol_ids.add(pol.linked_sol_id.id)
3670+
3671+ return list(sol_ids)
3672+
3673+ def compute_confirmed_delivery_date(self, cr, uid, ids, confirmed, prep_lt, ship_lt, est_transport_lead_time, db_date_format, context=None):
3674+ '''
3675+ compute the confirmed date
3676+
3677+ confirmed must be string
3678+ return string corresponding to database format
3679+ '''
3680+ assert type(confirmed) == str
3681+ confirmed = datetime.strptime(confirmed, db_date_format)
3682+ confirmed = confirmed + relativedelta(days=prep_lt or 0)
3683+ confirmed = confirmed + relativedelta(days=ship_lt or 0)
3684+ confirmed = confirmed + relativedelta(days=est_transport_lead_time or 0)
3685+ confirmed = confirmed.strftime(db_date_format)
3686+
3687+ return confirmed
3688+
3689+ def has_stockable_product(self,cr, uid, ids, *args):
3690+ '''
3691+ Override the has_stockable_product to return False
3692+ when the order_type of the order is 'direct'
3693+ '''
3694+
3695+ # TODO: See with Synchro team which object the system will should create
3696+ # to have an Incoming Movement in the destination instance
3697+ for order in self.read(cr, uid, ids, ['order_type']):
3698+ if order['order_type'] != 'direct':
3699+ for order in self.browse(cr, uid, ids):
3700+ for order_line in order.order_line:
3701+ if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
3702+ return True
3703+ return False
3704+
3705+ def ensure_object(self, cr, uid, model, value):
3706+ if isinstance(value, (int, long)):
3707+ value = self.pool.get(model).browse(cr, uid, value)
3708+ return value
3709+
3710+ def get_reason_type_id(self, cr, uid, order, context=None):
3711+
3712+ def get_reference(reason):
3713+ return self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', reason)[1]
3714+
3715+ order = self.ensure_object(cr, uid, 'purchase.order', order)
3716+
3717+ reason_type_id = False
3718+ if order.order_type in ('regular', 'purchase_list', 'direct') and \
3719+ order.partner_id.partner_type in ('internal', 'intermission', 'section', 'esc'):
3720+ reason_type_id = get_reference('reason_type_internal_supply')
3721+ elif order.order_type in ('regular', 'purchase_list', 'direct'):
3722+ reason_type_id = get_reference('reason_type_external_supply')
3723+ elif order.order_type == 'loan':
3724+ reason_type_id = get_reference('reason_type_loan')
3725+ elif order.order_type == 'donation_st':
3726+ reason_type_id = get_reference('reason_type_donation')
3727+ elif order.order_type == 'donation_exp':
3728+ reason_type_id = get_reference('reason_type_donation_expiry')
3729+ elif order.order_type == 'in_kind':
3730+ reason_type_id = get_reference('reason_type_in_kind_donation')
3731+ return reason_type_id
3732+
3733+ def create_picking(self, cr, uid, order, context=None):
3734+ if context is None:
3735+ context = {}
3736+
3737+ order = self.ensure_object(cr, uid, 'purchase.order', order)
3738+
3739+ values = {
3740+ 'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.in'),
3741+ 'origin': order.name + ((order.origin and (':' + order.origin)) or ''),
3742+ 'type': 'in',
3743+ 'partner_id2': order.partner_id.id,
3744+ 'address_id': order.partner_address_id.id or False,
3745+ 'invoice_state': '2binvoiced' if order.invoice_method == 'picking' else 'none',
3746+ 'purchase_id': order.id,
3747+ 'company_id': order.company_id.id,
3748+ 'move_lines': [],
3749+ }
3750+
3751+ reason_type_id = self.get_reason_type_id(cr, uid, order, context)
3752+ if reason_type_id:
3753+ values.update({'reason_type_id': reason_type_id})
3754+
3755+ return self.pool.get('stock.picking').create(cr, uid, values, context=context)
3756+
3757+ def create_picking_line(self, cr, uid, picking_id, order_line, context=None):
3758+ if context is None:
3759+ context = {}
3760+
3761+ order_line = self.ensure_object(cr, uid, 'purchase.order.line', order_line)
3762+ if not order_line:
3763+ return False
3764+
3765+ picking_id = self.ensure_object(cr, uid, 'stock.picking', picking_id)
3766+ order = order_line.order_id
3767+ dest = order.location_id.id
3768+
3769+ move_obj = self.pool.get('stock.move')
3770+ data_obj = self.pool.get('ir.model.data')
3771+
3772+ # service with reception are directed to Service Location
3773+ if order_line.product_id.type == 'service_recep' and not order.cross_docking_ok:
3774+ dest = self.pool.get('stock.location').get_service_location(cr, uid)
3775+ else:
3776+ sol = order_line.linked_sol_id
3777+ if sol:
3778+ if not (sol.order_id and sol.order_id.procurement_request):
3779+ pass
3780+ elif order_line.product_id.type == 'service_recep':
3781+ dest = self.pool.get('stock.location').get_service_location(cr, uid)
3782+ elif order_line.product_id.type == 'consu':
3783+ dest = data_obj.get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')[1]
3784+ elif sol.order_id.location_requestor_id.usage != 'customer':
3785+ dest = data_obj.get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_input')[1]
3786+
3787+ values = {
3788+ 'name': ''.join((order.name, ': ', (order_line.name or ''))),
3789+ 'product_id': order_line.product_id.id,
3790+ 'product_qty': order_line.product_qty,
3791+ 'product_uos_qty': order_line.product_qty,
3792+ 'product_uom': order_line.product_uom.id,
3793+ 'product_uos': order_line.product_uom.id,
3794+ 'location_id': order.partner_id.property_stock_supplier.id,
3795+ 'location_dest_id': dest,
3796+ 'picking_id': picking_id.id,
3797+ 'move_dest_id': order_line.move_dest_id.id,
3798+ 'state': 'draft',
3799+ 'purchase_line_id': order_line.id,
3800+ 'company_id': order.company_id.id,
3801+ 'price_currency_id': order.pricelist_id.currency_id.id,
3802+ 'price_unit': order_line.price_unit,
3803+ 'date': order_line.confirmed_delivery_date,
3804+ 'date_expected': order_line.confirmed_delivery_date or order_line.date_planned,
3805+ 'line_number': order_line.line_number,
3806+ 'comment': order_line.comment,
3807+ }
3808+
3809+ if picking_id.reason_type_id:
3810+ values.update({'reason_type_id': picking_id.reason_type_id.id})
3811+
3812+ ctx = context.copy()
3813+ ctx['bypass_store_function'] = [
3814+ ('stock.picking', ['dpo_incoming', 'dpo_out', 'overall_qty', 'line_state'])
3815+ ]
3816+
3817+ return move_obj.create(cr, uid, values, context=ctx)
3818+
3819+
3820+ def _get_location_id(self, cr, uid, vals, warehouse_id=False, context=None):
3821+ """
3822+ Get the location_id according to the cross_docking_ok option
3823+ Return vals
3824+ """
3825+ if 'cross_docking_ok' not in vals:
3826+ return vals
3827+
3828+ stock_warehouse_obj = self.pool.get('stock.warehouse')
3829+ if not warehouse_id:
3830+ warehouse_id = stock_warehouse_obj.search(cr, uid, [], context=context)[0]
3831+
3832+ if isinstance(warehouse_id, (str,unicode)):
3833+ try:
3834+ warehouse_id = int(warehouse_id)
3835+ except ValueError:
3836+ raise osv.except_osv(
3837+ _('Error'),
3838+ _('The field \'warehouse_id\' is a float field but value is a string - Please contact your administrator'),
3839+ )
3840+
3841+ if not vals.get('cross_docking_ok', False):
3842+ vals.update({'location_id': stock_warehouse_obj.read(cr, uid, warehouse_id, ['lot_input_id'], context=context)['lot_input_id'][0]})
3843+ elif vals.get('cross_docking_ok', False):
3844+ vals.update({'location_id': self.pool.get('stock.location').get_cross_docking_location(cr, uid)})
3845+
3846+ return vals
3847+
3848+ def set_manually_done(self, cr, uid, ids, all_doc=True, context=None):
3849+ '''
3850+ Set the PO to done state
3851+ '''
3852+ wf_service = netsvc.LocalService("workflow")
3853+ so_obj = self.pool.get('sale.order')
3854+ move_obj = self.pool.get('stock.move')
3855+
3856+ if context is None:
3857+ context = {}
3858+ if isinstance(ids, (int, long)):
3859+ ids = [ids]
3860+
3861+ order_lines = []
3862+ for order in self.browse(cr, uid, ids, context=context):
3863+ for line in order.order_line:
3864+ order_lines.append(line.id)
3865+
3866+ # Done picking
3867+ for pick in order.picking_ids:
3868+ if pick.state not in ('cancel', 'done'):
3869+ wf_service.trg_validate(uid, 'stock.picking', pick.id, 'manually_done', cr)
3870+
3871+ # Done loan counterpart
3872+ if order.loan_id and order.loan_id.state not in ('cancel', 'done') and not context.get('loan_id', False) == order.id:
3873+ loan_context = context.copy()
3874+ loan_context.update({'loan_id': order.id})
3875+ so_obj.set_manually_done(cr, uid, order.loan_id.id, all_doc=all_doc, context=loan_context)
3876+
3877+ # Done stock moves
3878+ move_ids = move_obj.search(cr, uid, [('purchase_line_id', 'in', order_lines), ('state', 'not in', ('cancel', 'done'))], context=context)
3879+ move_obj.set_manually_done(cr, uid, move_ids, all_doc=all_doc, context=context)
3880+
3881+ # Cancel all procurement ordes which have generated one of these PO
3882+ proc_ids = self.pool.get('procurement.order').search(cr, uid, [('purchase_id', 'in', ids)], context=context)
3883+ for proc in self.pool.get('procurement.order').browse(cr, uid, proc_ids, context=context):
3884+ if proc.move_id and proc.move_id.id:
3885+ move_obj.write(cr, uid, [proc.move_id.id], {'state': 'cancel'}, context=context)
3886+ wf_service.trg_validate(uid, 'procurement.order', proc.id, 'subflow.cancel', cr)
3887+
3888+ if all_doc:
3889+ # Detach the PO from his workflow and set the state to done
3890+ for order_id in self.browse(cr, uid, ids, context=context):
3891+ if order_id.rfq_ok and order_id.state == 'draft':
3892+ wf_service.trg_validate(uid, 'purchase.order', order_id.id, 'purchase_cancel', cr)
3893+ elif order_id.tender_id:
3894+ raise osv.except_osv(_('Error'), _('You cannot \'Close\' a Request for Quotation attached to a tender. Please make the tender %s to \'Closed\' before !') % order_id.tender_id.name)
3895+ else:
3896+ wf_service.trg_delete(uid, 'purchase.order', order_id.id, cr)
3897+ # Search the method called when the workflow enter in last activity
3898+ wkf_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'purchase', 'act_done')[1]
3899+ activity = self.pool.get('workflow.activity').browse(cr, uid, wkf_id, context=context)
3900+ _eval_expr(cr, [uid, 'purchase.order', order_id.id], False, activity.action)
3901+
3902+ return True
3903+
3904+
3905+ def check_empty_po(self, cr, uid, ids, context=None):
3906+ """
3907+ If the PO is empty, return a wizard to ask user if he wants
3908+ cancel the whole PO
3909+ """
3910+ order_wiz_obj = self.pool.get('purchase.order.cancel.wizard')
3911+ data_obj = self.pool.get('ir.model.data')
3912+
3913+ for po in self.browse(cr, uid, ids, context=context):
3914+ if all(x.state in ('cancel', 'done') for x in po.order_line):
3915+ wiz_id = order_wiz_obj.create(cr, uid, {'order_id': po.id}, context=context)
3916+ if po.rfq_ok:
3917+ view_id = data_obj.get_object_reference(cr, uid, 'tender_flow', 'ask_rfq_cancel_wizard_form_view')[1]
3918+ else:
3919+ view_id = data_obj.get_object_reference(cr, uid, 'purchase_override', 'ask_po_cancel_wizard_form_view')[1]
3920+ context['view_id'] = False
3921+ return {'type': 'ir.actions.act_window',
3922+ 'res_model': 'purchase.order.cancel.wizard',
3923+ 'view_type': 'form',
3924+ 'view_mode': 'form',
3925+ 'view_id': [view_id],
3926+ 'res_id': wiz_id,
3927+ 'target': 'new',
3928+ 'context': context}
3929+
3930+ return {'type': 'ir.actions.act_window_close'}
3931+
3932+ def round_to_soq(self, cr, uid, ids, context=None):
3933+ """
3934+ Create a new thread to check for each line of the order if the quantity
3935+ is compatible with the SoQ rounding of the supplier catalogue or
3936+ product. If not compatible, update the quantity to match with SoQ rounding.
3937+ :param cr: Cursor to the database
3938+ :param uid: ID of the res.users that calls the method
3939+ :param ids: List of ID of sale.order to check and update
3940+ :param context: Context of the call
3941+ :return: True
3942+ """
3943+ th = threading.Thread(
3944+ target=self._do_round_to_soq,
3945+ args=(cr, uid, ids, context, True),
3946+ )
3947+ th.start()
3948+ th.join(5.0)
3949+
3950+ return True
3951+
3952+ def _do_round_to_soq(self, cr, uid, ids, context=None, use_new_cursor=False):
3953+ """
3954+ Check for each line of the order if the quantity is compatible
3955+ with the SoQ rounding of the supplier catalogue or product. If
3956+ not compatible, update the quantity to match with SoQ rounding.
3957+ :param cr: Cursor to the database
3958+ :param uid: ID of the res.users that calls the method
3959+ :param ids: List of ID of sale.order to check and update
3960+ :param context: Context of the call
3961+ :param use_new_cursor: True if this method is called into a new thread
3962+ :return: True
3963+ """
3964+ pol_obj = self.pool.get('purchase.order.line')
3965+ uom_obj = self.pool.get('product.uom')
3966+ sup_obj = self.pool.get('product.supplierinfo')
3967+
3968+ if context is None:
3969+ context = {}
3970+
3971+ if isinstance(ids, (int, long)):
3972+ ids = [ids]
3973+
3974+ if use_new_cursor:
3975+ cr = pooler.get_db(cr.dbname).cursor()
3976+
3977+ try:
3978+ self.write(cr, uid, ids, {
3979+ 'update_in_progress': True,
3980+ }, context=context)
3981+ if use_new_cursor:
3982+ cr.commit()
3983+
3984+ pol_ids = pol_obj.search(cr, uid, [
3985+ ('order_id', 'in', ids),
3986+ ('product_id', '!=', False),
3987+ ], context=context)
3988+
3989+ to_update = {}
3990+ for pol in pol_obj.browse(cr, uid, pol_ids, context=context):
3991+ # Check only products with defined SoQ quantity
3992+ sup_ids = sup_obj.search(cr, uid, [
3993+ ('name', '=', pol.order_id.partner_id.id),
3994+ ('product_id', '=', pol.product_id.id),
3995+ ], context=context)
3996+ if not sup_ids and not pol.product_id.soq_quantity:
3997+ continue
3998+
3999+ # Get SoQ value
4000+ soq = pol.product_id.soq_quantity
4001+ soq_uom = pol.product_id.uom_id
4002+ if sup_ids:
4003+ for sup in sup_obj.browse(cr, uid, sup_ids, context=context):
4004+ for pcl in sup.pricelist_ids:
4005+ if pcl.rounding and pcl.min_quantity <= pol.product_qty:
4006+ soq = pcl.rounding
4007+ soq_uom = pcl.uom_id
4008+
4009+ if not soq:
4010+ continue
4011+
4012+ # Get line quantity in SoQ UoM
4013+ line_qty = pol.product_qty
4014+ if pol.product_uom.id != soq_uom.id:
4015+ line_qty = uom_obj._compute_qty_obj(cr, uid, pol.product_uom, pol.product_qty, soq_uom, context=context)
4016+
4017+ good_quantity = 0
4018+ if line_qty % soq:
4019+ good_quantity = (line_qty - (line_qty % soq)) + soq
4020+
4021+ if good_quantity and pol.product_uom.id != soq_uom.id:
4022+ good_quantity = uom_obj._compute_qty_obj(cr, uid, soq_uom, good_quantity, pol.product_uom, context=context)
4023+
4024+ if good_quantity:
4025+ to_update.setdefault(good_quantity, [])
4026+ to_update[good_quantity].append(pol.id)
4027+
4028+ for qty, line_ids in to_update.iteritems():
4029+ pol_obj.write(cr, uid, line_ids, {
4030+ 'product_qty': qty,
4031+ 'soq_updated': True,
4032+ }, context=context)
4033+ except Exception as e:
4034+ logger = logging.getLogger('purchase.order.round_to_soq')
4035+ logger.error(e)
4036+ finally:
4037+ self.write(cr, uid, ids, {
4038+ 'update_in_progress': False,
4039+ }, context=context)
4040+
4041+ if use_new_cursor:
4042+ cr.commit()
4043+ cr.close(True)
4044+
4045+ return True
4046+
4047+#CTRL
4048+ def update_supplier_info(self, cr, uid, ids, context=None, *args, **kwargs):
4049+ '''
4050+ update the supplier info of corresponding products
4051+ '''
4052+ info_obj = self.pool.get('product.supplierinfo')
4053+ pricelist_info_obj = self.pool.get('pricelist.partnerinfo')
4054+ for rfq in self.browse(cr, uid, ids, context=context):
4055+ for line in rfq.order_line:
4056+ # if the price is updated and a product selected
4057+ if line.price_unit and line.product_id:
4058+ # get the product
4059+ product = line.product_id
4060+ # find the corresponding suppinfo with sequence -99
4061+ info_99_list = info_obj.search(cr, uid, [('product_id', '=', product.product_tmpl_id.id),
4062+ ('sequence', '=', -99)],
4063+ order='NO_ORDER', context=context)
4064+
4065+ if info_99_list:
4066+ # we drop it
4067+ info_obj.unlink(cr, uid, info_99_list, context=context)
4068+
4069+ # create the new one
4070+ values = {'name': rfq.partner_id.id,
4071+ 'product_name': False,
4072+ 'product_code': False,
4073+ 'sequence' : -99,
4074+ #'product_uom': line.product_uom.id,
4075+ #'min_qty': 0.0,
4076+ #'qty': function
4077+ 'product_id' : product.product_tmpl_id.id,
4078+ 'delay' : int(rfq.partner_id.default_delay),
4079+ #'pricelist_ids': created just after
4080+ #'company_id': default value
4081+ }
4082+
4083+ new_info_id = info_obj.create(cr, uid, values, context=context)
4084+ # price lists creation - 'pricelist.partnerinfo
4085+ values = {'suppinfo_id': new_info_id,
4086+ 'min_quantity': 1.00,
4087+ 'price': line.price_unit,
4088+ 'uom_id': line.product_uom.id,
4089+ 'currency_id': line.currency_id.id,
4090+ 'valid_till': rfq.valid_till,
4091+ 'purchase_order_line_id': line.id,
4092+ 'comment': 'RfQ original quantity for price : %s' % line.product_qty,
4093+ }
4094+ pricelist_info_obj.create(cr, uid, values, context=context)
4095+
4096+ return True
4097+
4098+ def generate_po_from_rfq(self, cr, uid, ids, context=None):
4099+ '''
4100+ generate a po from the selected request for quotation
4101+ '''
4102+ # Objects
4103+ line_obj = self.pool.get('purchase.order.line')
4104+
4105+ # Some verifications
4106+ if context is None:
4107+ context = {}
4108+ if isinstance(ids, (int, long)):
4109+ ids = [ids]
4110+
4111+ # update price lists
4112+ self.update_supplier_info(cr, uid, ids, context=context)
4113+ # copy the po with rfq_ok set to False
4114+ data = self.read(cr, uid, ids[0], ['name', 'amount_total'], context=context)
4115+ if not data.get('amount_total', 0.00):
4116+ raise osv.except_osv(
4117+ _('Error'),
4118+ _('Generation of PO aborted because no price defined on lines.'),
4119+ )
4120+ new_po_id = self.copy(cr, uid, ids[0], {'name': False, 'rfq_ok': False, 'origin': data['name']}, context=dict(context,keepOrigin=True))
4121+ # Remove lines with 0.00 as unit price
4122+ no_price_line_ids = line_obj.search(cr, uid, [
4123+ ('order_id', '=', new_po_id),
4124+ ('price_unit', '=', 0.00),
4125+ ], order='NO_ORDER', context=context)
4126+ line_obj.unlink(cr, uid, no_price_line_ids, context=context)
4127+
4128+ data = self.read(cr, uid, new_po_id, ['name'], context=context)
4129+ # log message describing the previous action
4130+ self.log(cr, uid, new_po_id, _('The Purchase Order %s has been generated from Request for Quotation.')%data['name'])
4131+ # close the current po
4132+ wf_service = netsvc.LocalService("workflow")
4133+ wf_service.trg_validate(uid, 'purchase.order', ids[0], 'rfq_done', cr)
4134+
4135+ return new_po_id
4136+
4137+
4138+ def create_commitment_voucher_from_po(self, cr, uid, ids, context=None):
4139+ '''
4140+ Create a new commitment voucher from the given PO
4141+ @param ids id of the Purchase order
4142+ '''
4143+ if context is None:
4144+ context = {}
4145+ if isinstance(ids, (int,long)):
4146+ ids = [ids]
4147+
4148+ commit_id = False
4149+ for po in self.pool.get('purchase.order').browse(cr, uid, ids, context=context):
4150+ engagement_ids = self.pool.get('account.analytic.journal').search(cr, uid, [
4151+ ('type', '=', 'engagement'),
4152+ ('instance_id', '=', self.pool.get('res.users').browse(cr, uid, uid, context).company_id.instance_id.id)
4153+ ], limit=1, context=context)
4154+
4155+ vals = {
4156+ 'journal_id': engagement_ids and engagement_ids[0] or False,
4157+ 'currency_id': po.currency_id and po.currency_id.id or False,
4158+ 'partner_id': po.partner_id and po.partner_id.id or False,
4159+ 'purchase_id': po.id or False,
4160+ 'type': 'external' if po.partner_id.partner_type == 'external' else 'manual',
4161+ }
4162+ # prepare some values
4163+ today = time.strftime('%Y-%m-%d')
4164+ period_ids = get_period_from_date(self, cr, uid, po.delivery_confirmed_date or today, context=context)
4165+ period_id = period_ids and period_ids[0] or False
4166+ if not period_id:
4167+ raise osv.except_osv(_('Error'), _('No period found for given date: %s.') % (po.delivery_confirmed_date or today))
4168+ date = get_date_in_period(self, cr, uid, po.delivery_confirmed_date or today, period_id, context=context)
4169+ vals.update({
4170+ 'date': date,
4171+ 'period_id': period_id,
4172+ })
4173+ commit_id = self.pool.get('account.commitment').create(cr, uid, vals, context=context)
4174+
4175+ # Display a message to inform that a commitment was created
4176+ commit_data = self.pool.get('account.commitment').read(cr, uid, commit_id, ['name'], context=context)
4177+ message = _("Commitment Voucher %s has been created.") % commit_data.get('name', '')
4178+ view_ids = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution', 'account_commitment_form')
4179+ self.pool.get('account.commitment').log(cr, uid, commit_id, message, context={'view_id': view_ids and view_ids[1] or False})
4180+ self.infolog(cr, uid, message)
4181+
4182+ # Add analytic distribution from purchase
4183+ if po.analytic_distribution_id:
4184+ new_distrib_id = self.pool.get('analytic.distribution').copy(cr, uid, po.analytic_distribution_id.id, {}, context=context)
4185+ # Update this distribution not to have a link with purchase but with new commitment
4186+ if new_distrib_id:
4187+ self.pool.get('analytic.distribution').write(cr, uid, [new_distrib_id],
4188+ {'purchase_id': False, 'commitment_id': commit_id}, context=context)
4189+ # Create funding pool lines if needed
4190+ self.pool.get('analytic.distribution').create_funding_pool_lines(cr, uid, [new_distrib_id], context=context)
4191+ # Update commitment with new analytic distribution
4192+ self.pool.get('account.commitment').write(cr, uid, [commit_id], {'analytic_distribution_id': new_distrib_id}, context=context)
4193+
4194+ return commit_id
4195+
4196 purchase_order()
4197
4198-class purchase_order_line(osv.osv):
4199- def _amount_line(self, cr, uid, ids, prop, arg, context=None):
4200- res = {}
4201- cur_obj=self.pool.get('res.currency')
4202- tax_obj = self.pool.get('account.tax')
4203- for line in self.browse(cr, uid, ids, context=context):
4204- taxes = tax_obj.compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty)
4205- cur = line.order_id.pricelist_id.currency_id
4206- res[line.id] = cur_obj.round(cr, uid, cur.rounding, taxes['total'])
4207- if line.price_unit > 0 and res[line.id] < 0.01:
4208- res[line.id] = 0.01
4209- return res
4210-
4211- def _get_stock_take_date(self, cr, uid, context=None):
4212- '''
4213- Returns stock take date
4214- '''
4215- if context is None:
4216- context = {}
4217- order_obj = self.pool.get('purchase.order')
4218- res = False
4219-
4220- if context.get('purchase_id', False):
4221- po = order_obj.browse(cr, uid, context.get('purchase_id'), context=context)
4222- res = po.stock_take_date
4223-
4224- return res
4225-
4226- _columns = {
4227- 'name': fields.char('Description', size=256, required=True),
4228- 'product_qty': fields.float('Quantity', required=True, digits=(16,2)),
4229- 'date_planned': fields.date('Scheduled Date', required=True, select=True),
4230- 'taxes_id': fields.many2many('account.tax', 'purchase_order_taxe', 'ord_id', 'tax_id', 'Taxes'),
4231- 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, select=True),
4232- 'product_id': fields.many2one('product.product', 'Product', domain=[('purchase_ok','=',True)], change_default=True, select=True),
4233- 'move_ids': fields.one2many('stock.move', 'purchase_line_id', 'Reservation', readonly=True, ondelete='set null'),
4234- 'move_dest_id': fields.many2one('stock.move', 'Reservation Destination', ondelete='set null', select=True),
4235- 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Purchase Price')),
4236- 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', digits_compute= dp.get_precision('Purchase Price')),
4237- 'notes': fields.text('Notes'),
4238- 'order_id': fields.many2one('purchase.order', 'Order Reference', select=True, required=True, ondelete='cascade'),
4239- 'account_analytic_id':fields.many2one('account.analytic.account', 'Analytic Account',),
4240- 'company_id': fields.related('order_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
4241- 'state': fields.selection([('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'State', required=True, readonly=True,
4242- help=' * The \'Draft\' state is set automatically when purchase order in draft state. \
4243- \n* The \'Confirmed\' state is set automatically as confirm when purchase order in confirm state. \
4244- \n* The \'Done\' state is set automatically when purchase order is set as done. \
4245- \n* The \'Cancelled\' state is set automatically when user cancel purchase order.'),
4246- 'invoice_lines': fields.many2many('account.invoice.line', 'purchase_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
4247- 'invoiced': fields.boolean('Invoiced', readonly=True),
4248- 'partner_id': fields.related('order_id','partner_id',string='Partner',readonly=True,type="many2one", relation="res.partner", store=True),
4249- 'date_order': fields.related('order_id','date_order',string='Order Date',readonly=True,type="date"),
4250- 'stock_take_date': fields.date(string='Date of Stock Take', required=False),
4251- }
4252- _defaults = {
4253- 'product_qty': lambda *a: 1.0,
4254- 'state': lambda *args: 'draft',
4255- 'invoiced': lambda *a: 0,
4256- 'stock_take_date': _get_stock_take_date,
4257- }
4258- _table = 'purchase_order_line'
4259- _name = 'purchase.order.line'
4260- _description = 'Purchase Order Line'
4261-
4262- def copy_data(self, cr, uid, id, default=None, context=None):
4263- if not default:
4264- default = {}
4265- default.update({'state':'draft', 'move_ids':[],'invoiced':0,'invoice_lines':[]})
4266- return super(purchase_order_line, self).copy_data(cr, uid, id, default, context)
4267-
4268- def _hook_product_id_change(self, cr, uid, *args, **kwargs):
4269- '''
4270- Override the computation of product qty to order
4271- '''
4272- prod = kwargs['product']
4273- partner_id = kwargs['partner_id']
4274- qty = kwargs['product_qty']
4275-
4276- product_uom_pool = self.pool.get('product.uom')
4277-
4278- res = {}
4279- for s in prod.seller_ids:
4280- if s.name.id == partner_id:
4281- seller_delay = s.delay
4282- if s.product_uom:
4283- temp_qty = product_uom_pool._compute_qty(cr, uid, s.product_uom.id, s.min_qty, to_uom_id=prod.uom_id.id)
4284- uom = s.product_uom.id #prod_uom_po
4285- temp_qty = s.min_qty # supplier _qty assigned to temp
4286- if qty < temp_qty: # If the supplier quantity is greater than entered from user, set minimal.
4287- qty = temp_qty
4288- res.update({'warning': {'title': _('Warning'), 'message': _('The selected supplier has a minimal quantity set to %s, you cannot purchase less.') % qty}})
4289- qty_in_product_uom = product_uom_pool._compute_qty(cr, uid, uom, qty, to_uom_id=prod.uom_id.id)
4290- return res, qty_in_product_uom, qty, seller_delay
4291-
4292-
4293- def product_id_change(self, cr, uid, ids, pricelist, product, qty, uom,
4294- partner_id, date_order=False, fiscal_position=False, date_planned=False,
4295- name=False, price_unit=False, notes=False):
4296- if not pricelist:
4297- raise osv.except_osv(_('No Pricelist !'), _('You have to select a pricelist or a supplier in the purchase form !\nPlease set one before choosing a product.'))
4298- if not partner_id:
4299- raise osv.except_osv(_('No Partner!'), _('You have to select a partner in the purchase form !\nPlease set one partner before choosing a product.'))
4300- if not product:
4301- return {'value': {'price_unit': price_unit or 0.0, 'name': name or '',
4302- 'notes': notes or'', 'product_uom' : uom or False}, 'domain':{'product_uom':[]}}
4303- res = {}
4304- prod= self.pool.get('product.product').browse(cr, uid, product)
4305-
4306- lang=False
4307- if partner_id:
4308- lang=self.pool.get('res.partner').read(cr, uid, partner_id, ['lang'])['lang']
4309- context={'lang':lang}
4310- context['partner_id'] = partner_id
4311-
4312- prod = self.pool.get('product.product').browse(cr, uid, product, context=context)
4313- prod_uom_po = prod.uom_po_id.id
4314- if not uom:
4315- uom = prod_uom_po
4316- if not date_order:
4317- date_order = time.strftime('%Y-%m-%d')
4318- qty = qty or 1.0
4319- seller_delay = 0
4320-
4321- prod_name = self.pool.get('product.product').name_get(cr, uid, [prod.id], context=context)[0][1]
4322-
4323- res, qty_in_product_uom, qty, seller_delay = self._hook_product_id_change(cr, uid, product=prod, partner_id=partner_id,
4324- product_qty=qty, pricelist=pricelist, order_date=date_order, uom_id=uom,
4325- seller_delay=seller_delay, res=res, context=context)
4326-
4327- price = self.pool.get('product.pricelist').price_get(cr,uid,[pricelist],
4328- product, qty_in_product_uom or 1.0, partner_id, {
4329- 'uom': uom,
4330- 'date': date_order,
4331- })[pricelist]
4332- if price is False:
4333- warning = {
4334- 'title': 'No valid pricelist line found !',
4335- 'message':
4336- "Couldn't find a pricelist line matching this product and quantity.\n"
4337- "You have to change either the product, the quantity or the pricelist."
4338- }
4339- res.update({'warning': warning})
4340- dt = (datetime.now() + relativedelta(days=int(seller_delay) or 0.0)).strftime('%Y-%m-%d %H:%M:%S')
4341-
4342-
4343- res.update({'value': {'price_unit': price, 'name': prod_name,
4344- 'taxes_id':map(lambda x: x.id, prod.supplier_taxes_id),
4345- 'date_planned': date_planned or dt,'notes': notes or prod.description_purchase,
4346- 'product_qty': qty,
4347- 'product_uom': uom}})
4348- domain = {}
4349-
4350- taxes = self.pool.get('account.tax').browse(cr, uid,map(lambda x: x.id, prod.supplier_taxes_id))
4351- fpos = fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, fiscal_position) or False
4352- res['value']['taxes_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, taxes)
4353-
4354- res2 = self.pool.get('product.uom').read(cr, uid, [uom], ['category_id'])
4355- res3 = prod.uom_id.category_id.id
4356- domain = {'product_uom':[('category_id','=',res2[0]['category_id'][0])]}
4357- if res2[0]['category_id'][0] != res3:
4358- raise osv.except_osv(_('Wrong Product UOM !'), _('You have to select a product UOM in the same category than the purchase UOM of the product'))
4359-
4360- res['domain'] = domain
4361- return res
4362-
4363- def product_uom_change(self, cr, uid, ids, pricelist, product, qty, uom,
4364- partner_id, date_order=False, fiscal_position=False, date_planned=False,
4365- name=False, price_unit=False, notes=False):
4366- res = self.product_id_change(cr, uid, ids, pricelist, product, qty, uom,
4367- partner_id, date_order=date_order, fiscal_position=fiscal_position, date_planned=date_planned,
4368- name=name, price_unit=price_unit, notes=notes)
4369- if 'product_uom' in res['value']:
4370- if uom and (uom != res['value']['product_uom']) and res['value']['product_uom']:
4371- seller_uom_name = self.pool.get('product.uom').read(cr, uid, [res['value']['product_uom']], ['name'])[0]['name']
4372- res.update({'warning': {'title': _('Warning'), 'message': _('The selected supplier only sells this product by %s') % seller_uom_name }})
4373- del res['value']['product_uom']
4374- if not uom:
4375- res['value']['price_unit'] = 0.0
4376- return res
4377-
4378- def action_confirm(self, cr, uid, ids, context=None):
4379- self.write(cr, uid, ids, {'state': 'confirmed'}, context=context)
4380- return True
4381-
4382-purchase_order_line()
4383-
4384-class procurement_order(osv.osv):
4385- _inherit = 'procurement.order'
4386- _columns = {
4387- 'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
4388- }
4389-
4390- def action_po_assign(self, cr, uid, ids, context=None):
4391- """ This is action which call from workflow to assign purchase order to procurements
4392- @return: True
4393- """
4394- res = self.make_po(cr, uid, ids, context=context)
4395- res = res.values()
4396- return len(res) and res[0] or 0 #TO CHECK: why workflow is generated error if return not integer value
4397-
4398- def get_partner_hook(self, cr, uid, ids, context=None, *args, **kwargs):
4399- '''
4400- return a dictionary with partner, seller_qty and seller_delay
4401- '''
4402- result = {}
4403-
4404- procurement = kwargs['procurement']
4405- partner = procurement.product_id.seller_id # Taken Main Supplier of Product of Procurement.
4406- seller_qty = procurement.product_id.seller_qty
4407- seller_delay = int(procurement.product_id.seller_delay)
4408-
4409- result.update(partner=partner,
4410- seller_qty=seller_qty,
4411- seller_delay=seller_delay)
4412-
4413- return result
4414-
4415- def po_line_values_hook(self, cr, uid, ids, context=None, *args, **kwargs):
4416- '''
4417- Please copy this to your module's method also.
4418- This hook belongs to the make_po method from purchase>purchase.py>procurement_order
4419-
4420- - allow to modify the data for purchase order line creation
4421- '''
4422- line = kwargs['line']
4423- return line
4424-
4425- def po_values_hook(self, cr, uid, ids, context=None, *args, **kwargs):
4426- '''
4427- Please copy this to your module's method also.
4428- This hook belongs to the make_po method from purchase>purchase.py>procurement_order
4429-
4430- - allow to modify the data for purchase order creation
4431- '''
4432- values = kwargs['values']
4433- return values
4434-
4435- def create_po_hook(self, cr, uid, ids, context=None, *args, **kwargs):
4436- '''
4437- creation of purchase order
4438- return the id of newly created po
4439- '''
4440- po_obj = self.pool.get('purchase.order')
4441- values = kwargs['values']
4442- purchase_id = po_obj.create(cr, uid, values, context=context)
4443- return purchase_id
4444-
4445- def make_po(self, cr, uid, ids, context=None):
4446- """ Make purchase order from procurement
4447- @return: New created Purchase Orders procurement wise
4448- """
4449- res = {}
4450- if context is None:
4451- context = {}
4452- company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
4453- partner_obj = self.pool.get('res.partner')
4454- uom_obj = self.pool.get('product.uom')
4455- pricelist_obj = self.pool.get('product.pricelist')
4456- prod_obj = self.pool.get('product.product')
4457- acc_pos_obj = self.pool.get('account.fiscal.position')
4458- for procurement in self.browse(cr, uid, ids, context=context):
4459- res_id = procurement.move_id.id
4460-
4461- # partner, seller_qty and seller_delay are computed with hook
4462- hook = self.get_partner_hook(cr, uid, ids, context=context, procurement=procurement)
4463- partner = hook['partner']
4464- seller_qty = hook['seller_qty']
4465- seller_delay = hook['seller_delay']
4466- partner_id = partner.id
4467- address_id = partner_obj.address_get(cr, uid, [partner_id], ['delivery'])['delivery']
4468- pricelist_id = partner.property_product_pricelist_purchase
4469-
4470- uom_id = procurement.product_id.uom_po_id.id
4471-
4472- qty = uom_obj._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
4473- if seller_qty:
4474- qty = max(qty,seller_qty)
4475-
4476- price = pricelist_obj.price_get(cr, uid, [pricelist_id.id], procurement.product_id.id, qty, partner_id, {'uom': uom_id})[pricelist_id.id]
4477- if hook.get('price_unit', False):
4478- price = hook.get('price_unit', False)
4479-
4480- newdate = datetime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S')
4481- newdate = (newdate - relativedelta(days=int(company.po_lead))) - relativedelta(days=int(seller_delay))
4482-
4483- #Passing partner_id to context for purchase order line integrity of Line name
4484- context.update({'lang': partner.lang, 'partner_id': partner_id})
4485-
4486- product = prod_obj.browse(cr, uid, procurement.product_id.id, context=context)
4487-
4488- line = {
4489- 'name': product.partner_ref,
4490- 'product_qty': qty,
4491- 'product_id': procurement.product_id.id,
4492- 'product_uom': uom_id,
4493- 'price_unit': price,
4494- 'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
4495- 'move_dest_id': res_id,
4496- 'notes': product.description_purchase,
4497- }
4498-
4499- # line values modification from hook
4500- line = self.po_line_values_hook(cr, uid, ids, context=context, line=line, procurement=procurement, pricelist=pricelist_id)
4501-
4502- taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
4503- taxes = acc_pos_obj.map_tax(cr, uid, partner.property_account_position, taxes_ids)
4504- line.update({
4505- 'taxes_id': [(6,0,taxes)]
4506- })
4507- values = {
4508- 'origin': procurement.origin,
4509- 'partner_id': partner_id,
4510- 'partner_address_id': address_id,
4511- 'location_id': procurement.location_id.id,
4512- 'pricelist_id': pricelist_id.id,
4513- 'order_line': [(0,0,line)],
4514- 'company_id': procurement.company_id.id,
4515- 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False,
4516- }
4517- # values modification from hook
4518- values = self.po_values_hook(cr, uid, ids, context=context, values=values, procurement=procurement, line=line,)
4519- # purchase creation from hook
4520- purchase_id = self.create_po_hook(cr, uid, ids, context=context, values=values, procurement=procurement)
4521- res[procurement.id] = purchase_id
4522- self.write(cr, uid, [procurement.id], {'state': 'running', 'purchase_id': purchase_id})
4523- return res
4524-
4525-procurement_order()
4526
4527 class stock_invoice_onshipping(osv.osv_memory):
4528 _inherit = "stock.invoice.onshipping"
4529
4530=== modified file 'bin/addons/purchase/purchase_data.xml'
4531--- bin/addons/purchase/purchase_data.xml 2011-01-14 00:11:01 +0000
4532+++ bin/addons/purchase/purchase_data.xml 2017-10-02 12:34:11 +0000
4533@@ -1,7 +1,6 @@
4534 <?xml version="1.0" encoding="utf-8"?>
4535 <openerp>
4536 <data noupdate="1">
4537-
4538 <record id="req_link_purchase_order" model="res.request.link">
4539 <field name="name">Purchase Order</field>
4540 <field name="object">purchase.order</field>
4541@@ -37,6 +36,48 @@
4542 id="purchase_default_set"
4543 model="ir.values"
4544 name="set"/>
4545-
4546+ </data>
4547+
4548+ <data noupdate="0">
4549+ <record id="pol_state_cancel" model="purchase.order.line.state">
4550+ <field name="name">cancel</field>
4551+ <field name="sequence">1000</field>
4552+ </record>
4553+ <record id="pol_state_cancel_r" model="purchase.order.line.state">
4554+ <field name="name">cancel_r</field>
4555+ <field name="sequence">1001</field>
4556+ </record>
4557+ <record id="pol_state_draft" model="purchase.order.line.state">
4558+ <field name="name">draft</field>
4559+ <field name="sequence">10</field>
4560+ </record>
4561+ <record id="pol_state_validated_n" model="purchase.order.line.state">
4562+ <field name="name">validated_n</field>
4563+ <field name="sequence">15</field>
4564+ </record>
4565+ <record id="pol_state_validated" model="purchase.order.line.state">
4566+ <field name="name">validated</field>
4567+ <field name="sequence">20</field>
4568+ </record>
4569+ <record id="pol_state_sourced_sy" model="purchase.order.line.state">
4570+ <field name="name">sourced_sy</field>
4571+ <field name="sequence">40</field>
4572+ </record>
4573+ <record id="pol_state_sourced_v" model="purchase.order.line.state">
4574+ <field name="name">sourced_v</field>
4575+ <field name="sequence">50</field>
4576+ </record>
4577+ <record id="pol_state_sourced_n" model="purchase.order.line.state">
4578+ <field name="name">sourced_n</field>
4579+ <field name="sequence">60</field>
4580+ </record>
4581+ <record id="pol_state_confirmed" model="purchase.order.line.state">
4582+ <field name="name">confirmed</field>
4583+ <field name="sequence">70</field>
4584+ </record>
4585+ <record id="pol_state_done" model="purchase.order.line.state">
4586+ <field name="name">done</field>
4587+ <field name="sequence">80</field>
4588+ </record>
4589 </data>
4590 </openerp>
4591
4592=== added file 'bin/addons/purchase/purchase_line.py'
4593--- bin/addons/purchase/purchase_line.py 1970-01-01 00:00:00 +0000
4594+++ bin/addons/purchase/purchase_line.py 2017-10-02 12:34:11 +0000
4595@@ -0,0 +1,1422 @@
4596+# -*- coding: utf-8 -*-
4597+
4598+import time
4599+from datetime import datetime
4600+from dateutil.relativedelta import relativedelta
4601+
4602+from osv import osv, fields
4603+from tools.translate import _
4604+import decimal_precision as dp
4605+from purchase_override import PURCHASE_ORDER_STATE_SELECTION, PURCHASE_ORDER_LINE_STATE_SELECTION
4606+
4607+
4608+class purchase_order_line(osv.osv):
4609+ _table = 'purchase_order_line'
4610+ _name = 'purchase.order.line'
4611+ _description = 'Purchase Order Line'
4612+
4613+ def _get_vat_ok(self, cr, uid, ids, field_name, args, context=None):
4614+ '''
4615+ Return True if the system configuration VAT management is set to True
4616+ '''
4617+ vat_ok = self.pool.get('unifield.setup.configuration').get_config(cr, uid).vat_ok
4618+ res = {}
4619+ for id in ids:
4620+ res[id] = vat_ok
4621+
4622+ return res
4623+
4624+ def _amount_line(self, cr, uid, ids, prop, arg, context=None):
4625+ res = {}
4626+ cur_obj = self.pool.get('res.currency')
4627+ tax_obj = self.pool.get('account.tax')
4628+ for line in self.browse(cr, uid, ids, context=context):
4629+ taxes = tax_obj.compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty)
4630+ cur = line.order_id.pricelist_id.currency_id
4631+ res[line.id] = cur_obj.round(cr, uid, cur.rounding, taxes['total'])
4632+ if line.price_unit > 0 and res[line.id] < 0.01:
4633+ res[line.id] = 0.01
4634+ return res
4635+
4636+ def _get_price_change_ok(self, cr, uid, ids, field_name, args, context=None):
4637+ '''
4638+ Returns True if the price can be changed by the user
4639+ '''
4640+ res = {}
4641+
4642+ for line in self.browse(cr, uid, ids, context=context):
4643+ res[line.id] = True
4644+ stages = self._get_stages_price(cr, uid, line.product_id.id, line.product_uom.id, line.order_id,
4645+ context=context)
4646+ if line.merged_id and len(
4647+ line.merged_id.order_line_ids) > 1 and line.order_id.state != 'confirmed' and stages and not line.order_id.rfq_ok:
4648+ res[line.id] = False
4649+
4650+ return res
4651+
4652+ def _get_fake_state(self, cr, uid, ids, field_name, args, context=None):
4653+ if isinstance(ids, (int, long)):
4654+ ids = [ids]
4655+ ret = {}
4656+ for pol in self.read(cr, uid, ids, ['state']):
4657+ ret[pol['id']] = pol['state']
4658+ return ret
4659+
4660+ def _get_fake_id(self, cr, uid, ids, field_name, args, context=None):
4661+ if isinstance(ids, (int, long)):
4662+ ids = [ids]
4663+ ret = {}
4664+ for pol in self.read(cr, uid, ids, ['id']):
4665+ ret[pol['id']] = pol['id']
4666+ return ret
4667+
4668+ def _vals_get(self, cr, uid, ids, fields, arg, context=None):
4669+ '''
4670+ multi fields function method
4671+ '''
4672+ # Some verifications
4673+ if context is None:
4674+ context = {}
4675+ if isinstance(ids, (int, long)):
4676+ ids = [ids]
4677+
4678+ result = {}
4679+ for obj in self.browse(cr, uid, ids, context=context):
4680+ # default values
4681+ result[obj.id] = {'order_state_purchase_order_line': False}
4682+ # order_state_purchase_order_line
4683+ if obj.order_id:
4684+ result[obj.id].update({'order_state_purchase_order_line': obj.order_id.state})
4685+
4686+ return result
4687+
4688+ def _get_project_po_ref(self, cr, uid, ids, field_name, args, context=None):
4689+ '''
4690+ Return the name of the PO at project side
4691+ '''
4692+ if isinstance(ids, (int, long)):
4693+ ids = [ids]
4694+
4695+ res = dict.fromkeys(ids, '')
4696+ for line_id in ids:
4697+ sol_ids = self.get_sol_ids_from_pol_ids(cr, uid, line_id, context=context)
4698+ for sol in self.pool.get('sale.order.line').browse(cr, uid, sol_ids, context=context):
4699+ if sol.order_id and sol.order_id.client_order_ref:
4700+ if res[line_id]:
4701+ res[line_id] += ' - '
4702+ res[line_id] += sol.order_id.client_order_ref
4703+
4704+ return res
4705+
4706+ def _get_link_sol_id(self, cr, uid, ids, field_name, args, context=None):
4707+ """
4708+ Return the ID of the first FO line sourced by this PO line
4709+ """
4710+ if isinstance(ids, (int, long)):
4711+ ids = [ids]
4712+
4713+ res = dict.fromkeys(ids, False)
4714+ for line_id in ids:
4715+ sol_ids = self.get_sol_ids_from_pol_ids(cr, uid, [line_id], context=context)
4716+ if sol_ids:
4717+ res[line_id] = sol_ids[0]
4718+
4719+ return res
4720+
4721+ def _get_customer_ref(self, cr, uid, ids, field_name, args, context=None):
4722+ '''
4723+ Return the customer ref from "sale.order".client_order_ref
4724+ '''
4725+ if isinstance(ids, (int, long)):
4726+ ids = [ids]
4727+
4728+ res = {}
4729+ for pol in self.browse(cr, uid, ids, context=context):
4730+ res[
4731+ pol.id] = pol.linked_sol_id and pol.linked_sol_id.order_id.client_order_ref or False
4732+
4733+ return res
4734+
4735+ def _get_state_to_display(self, cr, uid, ids, field_name, args, context=None):
4736+ '''
4737+ return the purchase.order.line state to display
4738+ '''
4739+ if context is None:
4740+ context = {}
4741+ if isinstance(ids, (int,long)):
4742+ ids = [ids]
4743+
4744+ res = {}
4745+ for pol in self.browse(cr, uid, ids, context=context):
4746+ # if PO line has been created from ressourced process, then we display the state as 'Resourced-XXX' (excepted for 'done' status)
4747+ if pol.resourced_original_line and pol.state != 'done':
4748+ if pol.state.startswith('validated'):
4749+ res[pol.id] = 'Resourced-v'
4750+ elif pol.state.startswith('sourced'):
4751+ if pol.state == 'sourced_v':
4752+ res[pol.id] = 'Resourced-pv'
4753+ elif pol.state == 'sourced_sy':
4754+ res[pol.id] = 'Resourced-sy'
4755+ else:
4756+ res[pol.id] = 'Resourced-s'
4757+ elif pol.state.startswith('confirmed'):
4758+ res[pol.id] = 'Resourced-c'
4759+ else: # draft + unexpected PO line state
4760+ res[pol.id] = 'Resourced-d'
4761+ else: # state_to_display == state
4762+ res[pol.id] = self.pool.get('ir.model.fields').get_browse_selection(cr, uid, pol, 'state', context=context)
4763+
4764+ return res
4765+
4766+
4767+ def _get_display_resourced_orig_line(self, cr, uid, ids, field_name, args, context=None):
4768+ '''
4769+ return the original PO line (from which the current one has been resourced) formatted as wanted
4770+ '''
4771+ if context is None:
4772+ context = {}
4773+ if isinstance(ids, (int,long)):
4774+ ids = [ids]
4775+
4776+ res = {}
4777+ for pol in self.browse(cr, uid, ids, context=context):
4778+ res[pol.id] = False
4779+ if pol.resourced_original_line:
4780+ res[pol.id] = '%s' % (pol.resourced_original_line.line_number)
4781+
4782+ return res
4783+
4784+ def _get_stock_take_date(self, cr, uid, context=None):
4785+ '''
4786+ Returns stock take date
4787+ '''
4788+ if context is None:
4789+ context = {}
4790+ order_obj = self.pool.get('purchase.order')
4791+ res = False
4792+
4793+ if context.get('purchase_id', False):
4794+ po = order_obj.browse(cr, uid, context.get('purchase_id'), context=context)
4795+ res = po.stock_take_date
4796+
4797+ return res
4798+
4799+
4800+ _columns = {
4801+ 'set_as_sourced_n': fields.boolean(string='Set as Sourced-n', help='Line has been created further and has to be created back in preceding documents'),
4802+ 'set_as_validated_n': fields.boolean(string='Created when PO validated', help='Usefull for workflow transition to set the validated-n state'),
4803+ 'is_line_split': fields.boolean(string='This line is a split line?'),
4804+ 'linked_sol_id': fields.many2one('sale.order.line', string='Linked FO line', help='Linked Sale Order line in case of PO line from sourcing', readonly=True),
4805+ 'sync_linked_sol': fields.char(size=256, string='Linked FO line at synchro'),
4806+ # UTP-972: Use boolean to indicate if the line is a split line
4807+ 'merged_id': fields.many2one('purchase.order.merged.line', string='Merged line'),
4808+ 'origin': fields.char(size=512, string='Origin'),
4809+ 'link_so_id': fields.many2one('sale.order', string='Linked FO/IR', readonly=True),
4810+ 'dpo_received': fields.boolean(string='Is the IN has been received at Project side ?'),
4811+ 'change_price_ok': fields.function(_get_price_change_ok, type='boolean', method=True, string='Price changing'),
4812+ 'change_price_manually': fields.boolean(string='Update price manually'),
4813+ # openerp bug: eval invisible in p.o use the po line state and not the po state !
4814+ 'fake_state': fields.function(_get_fake_state, type='char', method=True, string='State',
4815+ help='for internal use only'),
4816+ # openerp bug: id is not given to onchanqge call if we are into one2many view
4817+ 'fake_id': fields.function(_get_fake_id, type='integer', method=True, string='Id',
4818+ help='for internal use only'),
4819+ 'old_price_unit': fields.float(string='Old price',
4820+ digits_compute=dp.get_precision('Purchase Price Computation')),
4821+ 'order_state_purchase_order_line': fields.function(_vals_get, method=True, type='selection',
4822+ selection=PURCHASE_ORDER_STATE_SELECTION,
4823+ string='State of Po', multi='get_vals_purchase_override',
4824+ store=False, readonly=True),
4825+
4826+ # This field is used to identify the FO PO line between 2 instances of the sync
4827+ 'sync_order_line_db_id': fields.text(string='Sync order line DB Id', required=False, readonly=True),
4828+ 'external_ref': fields.char(size=256, string='Ext. Ref.'),
4829+ 'project_ref': fields.char(size=256, string='Project Ref.'),
4830+ 'has_to_be_resourced': fields.boolean(string='Has to be re-sourced'),
4831+ 'select_fo': fields.many2one('sale.order', string='FO'),
4832+ 'fnct_project_ref': fields.function(_get_project_po_ref, method=True, string='Project PO',
4833+ type='char', size=128, store=False),
4834+ 'from_fo': fields.boolean(string='From FO', readonly=True),
4835+ 'display_sync_ref': fields.boolean(string='Display sync. ref.'),
4836+ 'instance_sync_order_ref': fields.many2one('sync.order.label', string='Order in sync. instance'),
4837+ 'link_sol_id': fields.function(_get_link_sol_id, method=True, type='many2one', relation='sale.order.line',
4838+ string='Linked FO line', store=False),
4839+ 'soq_updated': fields.boolean(string='SoQ updated', readonly=True),
4840+ 'red_color': fields.boolean(string='Red color'),
4841+ 'customer_ref': fields.function(_get_customer_ref, method=True, type="text", store=False,
4842+ string="Customer ref."),
4843+ 'name': fields.char('Description', size=256, required=True),
4844+ 'product_qty': fields.float('Quantity', required=True, digits=(16, 2)),
4845+ 'date_planned': fields.date('Scheduled Date', required=True, select=True),
4846+ 'taxes_id': fields.many2many('account.tax', 'purchase_order_taxe', 'ord_id', 'tax_id', 'Taxes'),
4847+ 'product_uom': fields.many2one('product.uom', 'Product UOM', required=True, select=True),
4848+ 'product_id': fields.many2one('product.product', 'Product', domain=[('purchase_ok', '=', True)],
4849+ change_default=True, select=True),
4850+ 'move_ids': fields.one2many('stock.move', 'purchase_line_id', 'Reservation', readonly=True,
4851+ ondelete='set null'),
4852+ 'move_dest_id': fields.many2one('stock.move', 'Reservation Destination', ondelete='set null', select=True),
4853+ 'price_unit': fields.float('Unit Price', required=True,
4854+ digits_compute=dp.get_precision('Purchase Price Computation')),
4855+ 'vat_ok': fields.function(_get_vat_ok, method=True, type='boolean', string='VAT OK', store=False,
4856+ readonly=True),
4857+ 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal',
4858+ digits_compute=dp.get_precision('Purchase Price')),
4859+ 'notes': fields.text('Notes'),
4860+ 'order_id': fields.many2one('purchase.order', 'Order Reference', select=True, required=True,
4861+ ondelete='cascade'),
4862+ 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic Account', ),
4863+ 'company_id': fields.related('order_id', 'company_id', type='many2one', relation='res.company',
4864+ string='Company', store=True, readonly=True),
4865+ 'state': fields.selection(PURCHASE_ORDER_LINE_STATE_SELECTION, 'State', required=True, readonly=True,
4866+ help=' * The \'Draft\' state is set automatically when purchase order in draft state. \
4867+ \n* The \'Confirmed\' state is set automatically as confirm when purchase order in confirm state. \
4868+ \n* The \'Done\' state is set automatically when purchase order is set as done. \
4869+ \n* The \'Cancelled\' state is set automatically when user cancel purchase order.'),
4870+ 'state_to_display': fields.function(_get_state_to_display, string='State', type='text', method=True, readonly=True,
4871+ help=' * The \'Draft\' state is set automatically when purchase order in draft state. \
4872+ \n* The \'Confirmed\' state is set automatically as confirm when purchase order in confirm state. \
4873+ \n* The \'Done\' state is set automatically when purchase order is set as done. \
4874+ \n* The \'Cancelled\' state is set automatically when user cancel purchase order.'
4875+ ),
4876+ 'resourced_original_line': fields.many2one('purchase.order.line', 'Original line', readonly=True, help='Original line from which the current one has been cancel and ressourced'),
4877+ 'display_resourced_orig_line': fields.function(_get_display_resourced_orig_line, method=True, type='char', readonly=True, string='Original PO line', help='Original line from which the current one has been cancel and ressourced'),
4878+ 'invoice_lines': fields.many2many('account.invoice.line', 'purchase_order_line_invoice_rel', 'order_line_id',
4879+ 'invoice_id', 'Invoice Lines', readonly=True),
4880+ 'invoiced': fields.boolean('Invoiced', readonly=True),
4881+ 'partner_id': fields.related('order_id','partner_id',string='Partner',readonly=True,type="many2one", relation="res.partner", store=True),
4882+ 'date_order': fields.related('order_id','date_order',string='Order Date',readonly=True,type="date"),
4883+ 'stock_take_date': fields.date(string='Date of Stock Take', required=False),
4884+ }
4885+ _defaults = {
4886+ 'set_as_sourced_n': lambda *a: False,
4887+ 'set_as_validated_n': lambda *a: False,
4888+ 'change_price_manually': lambda *a: False,
4889+ 'product_qty': lambda *a: 0.00,
4890+ 'price_unit': lambda *a: 0.00,
4891+ 'change_price_ok': lambda *a: True,
4892+ 'is_line_split': False, # UTP-972: by default not a split line
4893+ 'from_fo': lambda self, cr, uid, c: not c.get('rfq_ok', False) and c.get('from_fo', False),
4894+ 'soq_updated': False,
4895+ 'state': lambda *args: 'draft',
4896+ 'invoiced': lambda *a: 0,
4897+ 'vat_ok': lambda obj, cr, uid, context: obj.pool.get('unifield.setup.configuration').get_config(cr, uid).vat_ok,
4898+ 'stock_take_date': _get_stock_take_date,
4899+ }
4900+
4901+ def _get_destination_ok(self, cr, uid, lines, context):
4902+ dest_ok = False
4903+ for line in lines:
4904+ is_inkind = line.order_id and line.order_id.order_type == 'in_kind' or False
4905+ dest_ok = line.account_4_distribution and line.account_4_distribution.destination_ids or False
4906+ if not dest_ok:
4907+ if is_inkind:
4908+ raise osv.except_osv(_('Error'), _(
4909+ 'No destination found. An In-kind Donation account is probably missing for this line: %s.') % (
4910+ line.name or ''))
4911+ raise osv.except_osv(_('Error'), _('No destination found for this line: %s.') % (line.name or '',))
4912+ return dest_ok
4913+
4914+ def check_analytic_distribution(self, cr, uid, ids, context=None, create_missing=False):
4915+ """
4916+ Check analytic distribution validity for given PO line.
4917+ Also check that partner have a donation account (is PO is in_kind)
4918+ """
4919+ # Objects
4920+ ad_obj = self.pool.get('analytic.distribution')
4921+ ccdl_obj = self.pool.get('cost.center.distribution.line')
4922+ pol_obj = self.pool.get('purchase.order.line')
4923+ imd_obj = self.pool.get('ir.model.data')
4924+
4925+ if context is None:
4926+ context = {}
4927+ if isinstance(ids, (int, long)):
4928+ ids = [ids]
4929+
4930+ try:
4931+ intermission_cc = imd_obj.get_object_reference(cr, uid, 'analytic_distribution', 'analytic_account_project_intermission')[1]
4932+ except ValueError:
4933+ intermission_cc = 0
4934+
4935+ po_info = {}
4936+ for pol in self.browse(cr, uid, ids, context=context):
4937+ po = pol.order_id
4938+ if po.id not in po_info:
4939+ donation_intersection = po.order_type in ['donation_exp', 'donation_st'] and po.partner_type and po.partner_type == 'section'
4940+ if po.order_type == 'in_kind' or donation_intersection:
4941+ if not po.partner_id.donation_payable_account:
4942+ raise osv.except_osv(_('Error'), _('No donation account on this partner: %s') % (po.partner_id.name or '',))
4943+
4944+ if po.partner_id and po.partner_id.partner_type == 'intermission':
4945+ if not intermission_cc:
4946+ raise osv.except_osv(_('Error'), _('No Intermission Cost Center found!'))
4947+
4948+ po_info[po.id] = True
4949+
4950+
4951+ distrib = pol.analytic_distribution_id or po.analytic_distribution_id or False
4952+
4953+
4954+ # Raise an error if no analytic distribution found
4955+ if not distrib:
4956+ # UFTP-336: For the case of a new line added from Coordo, it's a push flow, no need to check the AD! VERY SPECIAL CASE
4957+ if po.order_type not in ('loan', 'donation_st', 'donation_exp', 'in_kind') or po.push_fo:
4958+ return True
4959+ if create_missing and po.partner_id and po.partner_id.partner_type == 'intermission':
4960+ # intermission push flow, new line added: AD needed
4961+ destination_id = pol.account_4_distribution and pol.account_4_distribution.default_destination_id and pol.account_4_distribution.default_destination_id.id or False
4962+ ad_obj.create(cr, uid, {
4963+ 'purchase_line_ids': [(4, pol.id)],
4964+ 'cost_center_lines': [(0, 0, {'destination_id': destination_id, 'analytic_id': intermission_cc , 'percentage':'100', 'currency_id': po.currency_id.id})]
4965+ })
4966+ else:
4967+ raise osv.except_osv(_('Warning'),
4968+ _('Analytic allocation is mandatory for this line: %s!') % (pol.name or '',))
4969+
4970+ elif pol.analytic_distribution_state != 'valid':
4971+ id_ad = ad_obj.create(cr, uid, {})
4972+ ad_lines = pol.analytic_distribution_id and pol.analytic_distribution_id.cost_center_lines or po.analytic_distribution_id.cost_center_lines
4973+ bro_dests = self._get_destination_ok(cr, uid, [pol], context=context)
4974+ for line in ad_lines:
4975+ # fetch compatible destinations then use on of them:
4976+ # - destination if compatible
4977+ # - else default destination of given account
4978+ if line.destination_id in bro_dests:
4979+ bro_dest_ok = line.destination_id
4980+ else:
4981+ bro_dest_ok = pol.account_4_distribution.default_destination_id
4982+ # Copy cost center line to the new distribution
4983+ ccdl_obj.copy(cr, uid, line.id, {
4984+ 'distribution_id': id_ad,
4985+ 'destination_id': bro_dest_ok.id,
4986+ 'partner_type': po.partner_id.partner_type,
4987+ })
4988+ # Write result
4989+ pol_obj.write(cr, uid, [pol.id], {'analytic_distribution_id': id_ad})
4990+ else:
4991+ ad_lines = pol.analytic_distribution_id and pol.analytic_distribution_id.cost_center_lines or po.analytic_distribution_id.cost_center_lines
4992+ line_ids_to_write = [line.id for line in ad_lines if not
4993+ line.partner_type]
4994+ ccdl_obj.write(cr, uid, line_ids_to_write, {
4995+ 'partner_type': pol.order_id.partner_id.partner_type,
4996+ })
4997+
4998+ return True
4999+
5000+ def _hook_product_id_change(self, cr, uid, *args, **kwargs):
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches