[Fix] sale: Fix edi issue
[odoo/odoo.git] / addons / sale / edi / sale_order.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Business Applications
5 #    Copyright (c) 2011 OpenERP S.A. <http://openerp.com>
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 from datetime import datetime, timedelta
23 from dateutil.relativedelta import relativedelta
24
25 from osv import fields, osv, orm
26 from edi import EDIMixin
27 from tools import DEFAULT_SERVER_DATE_FORMAT
28
29 SALE_ORDER_LINE_EDI_STRUCT = {
30     'sequence': True,
31     'name': True,
32     #custom: 'date_planned'
33     'product_id': True,
34     'product_uom': True,
35     'price_unit': True,
36     #custom: 'product_qty'
37     'discount': True,
38     'notes': True,
39
40     # fields used for web preview only - discarded on import
41     'price_subtotal': True,
42 }
43
44 SALE_ORDER_EDI_STRUCT = {
45     'name': True,
46     'origin': True,
47     'company_id': True, # -> to be changed into partner
48     #custom: 'partner_ref'
49     'date_order': True,
50     'partner_id': True,
51     #custom: 'partner_address'
52     #custom: 'notes'
53     'order_line': SALE_ORDER_LINE_EDI_STRUCT,
54
55     # fields used for web preview only - discarded on import
56     'amount_total': True,
57     'amount_untaxed': True,
58     'amount_tax': True,
59     'payment_term': True,
60     'order_policy': True,
61     'user_id': True,
62 }
63
64 class sale_order(osv.osv, EDIMixin):
65     _inherit = 'sale.order'
66
67     def edi_export(self, cr, uid, records, edi_struct=None, context=None):
68         """Exports a Sale order"""
69         edi_struct = dict(edi_struct or SALE_ORDER_EDI_STRUCT)
70         res_company = self.pool.get('res.company')
71         res_partner_address = self.pool.get('res.partner')
72         edi_doc_list = []
73         for order in records:
74             # generate the main report
75             self._edi_generate_report_attachment(cr, uid, order, context=context)
76
77             # Get EDI doc based on struct. The result will also contain all metadata fields and attachments.
78             edi_doc = super(sale_order,self).edi_export(cr, uid, [order], edi_struct, context)[0]
79             edi_doc.update({
80                     # force trans-typing to purchase.order upon import
81                     '__import_model': 'purchase.order',
82                     '__import_module': 'purchase',
83
84                     'company_address': res_company.edi_export_address(cr, uid, order.company_id, context=context),
85                     'partner_id': res_partner_address.edi_export(cr, uid, [order.partner_id], context=context)[0],
86
87                     'currency': self.pool.get('res.currency').edi_export(cr, uid, [order.pricelist_id.currency_id],
88                                                                          context=context)[0],
89                     'partner_ref': order.client_order_ref or False,
90                     'notes': order.note or False,
91             })
92             edi_doc_list.append(edi_doc)
93         return edi_doc_list
94
95
96     def _edi_import_company(self, cr, uid, edi_document, context=None):
97         # TODO: for multi-company setups, we currently import the document in the
98         #       user's current company, but we should perhaps foresee a way to select
99         #       the desired company among the user's allowed companies
100
101         self._edi_requires_attributes(('company_id','company_address'), edi_document)
102         res_partner = self.pool.get('res.partner')
103
104         # imported company = as a new partner
105         src_company_id, src_company_name = edi_document.pop('company_id')
106         partner_id = self.edi_import_relation(cr, uid, 'res.partner', src_company_name,
107                                               src_company_id, context=context)
108         partner_value = {'supplier': True}
109         res_partner.write(cr, uid, [partner_id], partner_value, context=context)
110
111         # imported company_address = new partner address
112         address_info = edi_document.pop('company_address')
113         address_info['parent_id'] = (src_company_id, src_company_name)
114         address_info['type'] = 'default'
115         address_id = res_partner.edi_import(cr, uid, address_info, context=context)
116
117         # modify edi_document to refer to new partner/address
118         partner_address = res_partner.browse(cr, uid, address_id, context=context)
119        # edi_document['parent_id'] = (src_company_id, src_company_name)
120         edi_document.pop('partner_address', False) # ignored
121         address_edi_m2o = self.edi_m2o(cr, uid, partner_address, context=context)
122         edi_document['partner_invoice_id'] = address_edi_m2o
123         edi_document['partner_shipping_id'] = address_edi_m2o
124
125         return partner_id
126
127     def _edi_get_pricelist(self, cr, uid, partner_id, currency, context=None):
128         # TODO: refactor into common place for purchase/sale, e.g. into product module
129         partner_model = self.pool.get('res.partner')
130         partner = partner_model.browse(cr, uid, partner_id, context=context)
131         pricelist = partner.property_product_pricelist
132         if not pricelist:
133             pricelist = self.pool.get('ir.model.data').get_object(cr, uid, 'product', 'list0', context=context)
134
135         if not pricelist.currency_id == currency:
136             # look for a pricelist with the right type and currency, or make a new one
137             pricelist_type = 'sale'
138             product_pricelist = self.pool.get('product.pricelist')
139             match_pricelist_ids = product_pricelist.search(cr, uid,[('type','=',pricelist_type),
140                                                                     ('currency_id','=',currency.id)])
141             if match_pricelist_ids:
142                 pricelist_id = match_pricelist_ids[0]
143             else:
144                 pricelist_name = _('EDI Pricelist (%s)') % (currency.name,)
145                 pricelist_id = product_pricelist.create(cr, uid, {'name': pricelist_name,
146                                                                   'type': pricelist_type,
147                                                                   'currency_id': currency.id,
148                                                                  })
149                 self.pool.get('product.pricelist.version').create(cr, uid, {'name': pricelist_name,
150                                                                             'pricelist_id': pricelist_id})
151             pricelist = product_pricelist.browse(cr, uid, pricelist_id)
152
153         return self.edi_m2o(cr, uid, pricelist, context=context)
154
155     def edi_import(self, cr, uid, edi_document, context=None):
156         self._edi_requires_attributes(('company_id','company_address','order_line','date_order','currency'), edi_document)
157
158         #import company as a new partner
159         partner_id = self._edi_import_company(cr, uid, edi_document, context=context)
160
161         # currency for rounding the discount calculations and for the pricelist
162         res_currency = self.pool.get('res.currency')
163         currency_info = edi_document.pop('currency')
164         currency_id = res_currency.edi_import(cr, uid, currency_info, context=context)
165         order_currency = res_currency.browse(cr, uid, currency_id)
166
167         date_order = edi_document['date_order']
168         partner_ref = edi_document.pop('partner_ref', False)
169         edi_document['client_order_ref'] = edi_document['name']
170         edi_document['name'] = partner_ref or edi_document['name']
171         edi_document['note'] = edi_document.pop('notes', False)
172         edi_document['pricelist_id'] = self._edi_get_pricelist(cr, uid, partner_id, order_currency, context=context)
173
174         # discard web preview fields, if present
175         edi_document.pop('amount_total', None)
176         edi_document.pop('amount_tax', None)
177         edi_document.pop('amount_untaxed', None)
178
179         order_lines = edi_document['order_line']
180         for order_line in order_lines:
181             self._edi_requires_attributes(('date_planned', 'product_id', 'product_uom', 'product_qty', 'price_unit'), order_line)
182             order_line['product_uom_qty'] = order_line['product_qty']
183             del order_line['product_qty']
184             date_planned = order_line.pop('date_planned')
185             delay = 0
186             if date_order and date_planned:
187                 # no security_days buffer, this is the promised date given by supplier
188                 delay = (datetime.strptime(date_planned, DEFAULT_SERVER_DATE_FORMAT) - \
189                          datetime.strptime(date_order, DEFAULT_SERVER_DATE_FORMAT)).days
190             order_line['delay'] = delay
191
192             # discard web preview fields, if present
193             order_line.pop('price_subtotal', None)
194         return super(sale_order,self).edi_import(cr, uid, edi_document, context=context)
195
196 class sale_order_line(osv.osv, EDIMixin):
197     _inherit='sale.order.line'
198
199     def edi_export(self, cr, uid, records, edi_struct=None, context=None):
200         """Overridden to provide sale order line fields with the expected names
201            (sale and purchase orders have different column names)"""
202         edi_struct = dict(edi_struct or SALE_ORDER_LINE_EDI_STRUCT)
203         edi_doc_list = []
204         for line in records:
205             edi_doc = super(sale_order_line,self).edi_export(cr, uid, [line], edi_struct, context)[0]
206             edi_doc['__import_model'] = 'purchase.order.line'
207             edi_doc['product_qty'] = line.product_uom_qty
208             if line.product_uos:
209                 edi_doc.update(product_uom=line.product_uos,
210                                product_qty=line.product_uos_qty)
211
212             # company.security_days is for internal use, so customer should only
213             # see the expected date_planned based on line.delay 
214             date_planned = datetime.strptime(line.order_id.date_order, DEFAULT_SERVER_DATE_FORMAT) + \
215                             relativedelta(days=line.delay or 0.0)
216             edi_doc['date_planned'] = date_planned.strftime(DEFAULT_SERVER_DATE_FORMAT)
217             edi_doc_list.append(edi_doc)
218         return edi_doc_list
219
220 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: