~ntrubert/openobject-addons/base-sale-multichannel-multilingual

« back to all changes in this revision

Viewing changes to sale.py

  • Committer: root
  • Date: 2010-08-26 06:59:12 UTC
  • Revision ID: root@towakoni-20100826065912-z45ck9n49ov0f9im
reference before modification

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- encoding: utf-8 -*-
 
2
#########################################################################
 
3
#                                                                       #
 
4
#########################################################################
 
5
#                                                                       #
 
6
# Copyright (C) 2009  Raphaël Valyi                                     #
 
7
#                                                                       #
 
8
#This program is free software: you can redistribute it and/or modify   #
 
9
#it under the terms of the GNU General Public License as published by   #
 
10
#the Free Software Foundation, either version 3 of the License, or      #
 
11
#(at your option) any later version.                                    #
 
12
#                                                                       #
 
13
#This program is distributed in the hope that it will be useful,        #
 
14
#but WITHOUT ANY WARRANTY; without even the implied warranty of         #
 
15
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          #
 
16
#GNU General Public License for more details.                           #
 
17
#                                                                       #
 
18
#You should have received a copy of the GNU General Public License      #
 
19
#along with this program.  If not, see <http://www.gnu.org/licenses/>.  #
 
20
#########################################################################
 
21
 
 
22
from osv import osv, fields
 
23
from base_external_referentials import external_osv
 
24
from sets import Set
 
25
import netsvc
 
26
from tools.translate import _
 
27
#from datetime import datetime
 
28
import time
 
29
 
 
30
 
 
31
class external_shop_group(external_osv.external_osv):
 
32
    _name = 'external.shop.group'
 
33
    _description = 'External Referential Shop Group'
 
34
 
 
35
    _columns = {
 
36
        'name': fields.char('Name', size=64, required=True),
 
37
        'referential_id': fields.many2one('external.referential', 'Referential', select=True, ondelete='cascade'),
 
38
        'shop_ids': fields.one2many('sale.shop', 'shop_group_id', 'Sale Shops'),
 
39
    }
 
40
 
 
41
external_shop_group()
 
42
 
 
43
 
 
44
class external_referential(osv.osv):
 
45
    _inherit = 'external.referential'
 
46
 
 
47
    _columns = {
 
48
        'shop_group_ids': fields.one2many('external.shop.group', 'referential_id', 'Sub Entities'),
 
49
    }
 
50
 
 
51
external_referential()
 
52
 
 
53
 
 
54
class product_category(osv.osv):
 
55
    _inherit = "product.category"
 
56
 
 
57
    def collect_children(self, category, children=[]):
 
58
        for child in category.child_id:
 
59
            children.append(child.id)
 
60
            self.collect_children(child, children)
 
61
        return children
 
62
 
 
63
    def _get_recursive_children_ids(self, cr, uid, ids, name, args, context=None):
 
64
        res = {}
 
65
        for category in self.browse(cr, uid, ids):
 
66
            res[category.id] = self.collect_children(category, [category.id])
 
67
        return res
 
68
 
 
69
    _columns = {
 
70
        'recursive_childen_ids': fields.function(_get_recursive_children_ids, method=True, type='one2many', relation="product.category", string='All Child Categories'),
 
71
    }
 
72
 
 
73
product_category()
 
74
 
 
75
 
 
76
class sale_shop(external_osv.external_osv):
 
77
    _inherit = "sale.shop"
 
78
 
 
79
    def _get_exportable_product_ids(self, cr, uid, ids, name, args, context=None):
 
80
        res = {}
 
81
        for shop in self.browse(cr, uid, ids):
 
82
            root_categories = [category for category in shop.exportable_root_category_ids]
 
83
            all_categories = []
 
84
            for category in root_categories:
 
85
                all_categories += [category.id for category in category.recursive_childen_ids]
 
86
 
 
87
            # If product_m2mcategories module is installed search in main category and extra categories. If not, only in main category
 
88
            cr.execute('select * from ir_module_module where name=%s and state=%s', ('product_m2mcategories','installed'))
 
89
            if cr.fetchone():
 
90
                product_ids = self.pool.get("product.product").search(cr, uid, ['|',('categ_id', 'in', all_categories),('categ_ids', 'in', all_categories)])
 
91
            else:
 
92
                product_ids = self.pool.get("product.product").search(cr, uid, [('categ_id', 'in', all_categories)])
 
93
            res[shop.id] = product_ids
 
94
        return res
 
95
 
 
96
    _columns = {
 
97
        #'exportable_category_ids': fields.function(_get_exportable_category_ids, method=True, type='one2many', relation="product.category", string='Exportable Categories'),
 
98
        'exportable_root_category_ids': fields.many2many('product.category', 'shop_category_rel', 'categ_id', 'shop_id', 'Exportable Root Categories'),
 
99
        'exportable_product_ids': fields.function(_get_exportable_product_ids, method=True, type='one2many', relation="product.product", string='Exportable Products'),
 
100
        'shop_group_id': fields.many2one('external.shop.group', 'Shop Group', ondelete='cascade'),
 
101
        'last_inventory_export_date': fields.datetime('Last Inventory Export Time'),
 
102
        'last_images_export_date': fields.datetime('Last Images Export Time'),
 
103
        'last_update_order_export_date' : fields.datetime('Last Order Update  Time'),
 
104
        'last_products_export_date' : fields.datetime('Last Product Export  Time'),
 
105
        'referential_id': fields.related('shop_group_id', 'referential_id', type='many2one', relation='external.referential', string='External Referential'),
 
106
        'is_tax_included': fields.boolean('Prices Include Tax?', help="Requires sale_tax_include module to be installed"),
 
107
        'picking_policy': fields.selection([('direct', 'Partial Delivery'), ('one', 'Complete Delivery')],
 
108
                                           'Packing Policy', help="""If you don't have enough stock available to deliver all at once, do you accept partial shipments or not?"""),
 
109
        'order_policy': fields.selection([
 
110
            ('prepaid', 'Payment Before Delivery'),
 
111
            ('manual', 'Shipping & Manual Invoice'),
 
112
            ('postpaid', 'Invoice on Order After Delivery'),
 
113
            ('picking', 'Invoice from the Packing'),
 
114
        ], 'Shipping Policy', help="""The Shipping Policy is used to synchronise invoice and delivery operations.
 
115
  - The 'Pay before delivery' choice will first generate the invoice and then generate the packing order after the payment of this invoice.
 
116
  - The 'Shipping & Manual Invoice' will create the packing order directly and wait for the user to manually click on the 'Invoice' button to generate the draft invoice.
 
117
  - The 'Invoice on Order After Delivery' choice will generate the draft invoice based on sale order after all packing lists have been finished.
 
118
  - The 'Invoice from the packing' choice is used to create an invoice during the packing process."""),
 
119
        'invoice_quantity': fields.selection([('order', 'Ordered Quantities'), ('procurement', 'Shipped Quantities')], 'Invoice on', help="The sale order will automatically create the invoice proposition (draft invoice). Ordered and delivered quantities may not be the same. You have to choose if you invoice based on ordered or shipped quantities. If the product is a service, shipped quantities means hours spent on the associated tasks."),
 
120
        'invoice_generation_policy': fields.selection([('none', 'None'), ('draft', 'Draft'), ('valid', 'Validated')], 'Invoice Generation Policy', help="Should orders create an invoice after import?"),
 
121
        'picking_generation_policy': fields.selection([('none', 'None'), ('draft', 'Draft'), ('valid', 'Validated')], 'Picking Generation Policy', help="Should orders create a picking after import?"),
 
122
    }
 
123
 
 
124
    _defaults = {
 
125
        'payment_default_id': lambda * a: 1, #required field that would cause trouble if not set when importing
 
126
        'picking_policy': lambda * a: 'direct',
 
127
        'order_policy': lambda * a: 'manual',
 
128
        'invoice_quantity': lambda * a: 'order',
 
129
        'invoice_generation_policy': lambda * a: 'draft',
 
130
        'picking_generation_policy': lambda * a: 'draft',
 
131
    }
 
132
 
 
133
    def _get_pricelist(self, cr, uid, shop):
 
134
        if shop.pricelist_id:
 
135
            return shop.pricelist_id.id
 
136
        else:
 
137
            return self.pool.get('product.pricelist').search(cr, uid, [('type', '=', 'sale'), ('active', '=', True)])[0]
 
138
 
 
139
    def export_categories(self, cr, uid, shop, ctx):
 
140
        categories = Set([])
 
141
        categ_ids = []
 
142
        for category in shop.exportable_root_category_ids:
 
143
            categ_ids = self.pool.get('product.category')._get_recursive_children_ids(cr, uid, [category.id], "", [], ctx)[category.id]
 
144
            for categ_id in categ_ids:
 
145
                categories.add(categ_id)
 
146
        ctx['shop_id'] = shop.id
 
147
        self.pool.get('product.category').ext_export(cr, uid, [categ_id for categ_id in categories], [shop.referential_id.id], {}, ctx)
 
148
 
 
149
    def export_products_collection(self, cr, uid, shop, products, ctx):
 
150
        self.pool.get('product.product').ext_export(cr, uid, [product.id for product in shop.exportable_product_ids] , [shop.referential_id.id], {}, ctx)
 
151
 
 
152
    def export_products(self, cr, uid, shop, ctx):
 
153
        self.export_products_collection(cr, uid, shop, shop.exportable_product_ids, ctx)
 
154
 
 
155
    def export_catalog(self, cr, uid, ids, ctx):
 
156
        for shop in self.browse(cr, uid, ids):
 
157
            ctx['shop_id'] = shop.id
 
158
            ctx['conn_obj'] = self.external_connection(cr, uid, shop.referential_id)
 
159
            self.export_categories(cr, uid, shop, ctx)
 
160
            self.export_products(cr, uid, shop, ctx)
 
161
        self.export_inventory(cr, uid, ids, ctx)
 
162
        return False
 
163
 
 
164
    def export_inventory(self, cr, uid, ids, ctx):
 
165
        for shop in self.browse(cr, uid, ids):
 
166
            ctx['shop_id'] = shop.id
 
167
            ctx['conn_obj'] = self.external_connection(cr, uid, shop.referential_id)
 
168
            product_ids = [product.id for product in shop.exportable_product_ids]
 
169
            if shop.last_inventory_export_date:
 
170
                recent_move_ids = self.pool.get('stock.move').search(cr, uid, [('date', '>', shop.last_inventory_export_date), ('product_id', 'in', product_ids)])
 
171
            else:
 
172
                recent_move_ids = self.pool.get('stock.move').search(cr, uid, [('product_id', 'in', product_ids)])
 
173
            product_ids = [move.product_id.id for move in self.pool.get('stock.move').browse(cr, uid, recent_move_ids)]
 
174
            res = self.pool.get('product.product').export_inventory(cr, uid, product_ids, '', ctx)
 
175
            self.pool.get('sale.shop').write(cr, uid, shop.id, {'last_inventory_export_date': time.strftime('%Y-%m-%d %H:%M:%S')})
 
176
        return res
 
177
 
 
178
    def import_catalog(self, cr, uid, ids, ctx):
 
179
        #TODO import categories, then products
 
180
        osv.except_osv(_("Not Implemented"), _("Not Implemented in abstract base module!"))
 
181
 
 
182
    def import_orders(self, cr, uid, ids, ctx):
 
183
        for shop in self.browse(cr, uid, ids):
 
184
            ctx['conn_obj'] = self.external_connection(cr, uid, shop.referential_id)
 
185
            defaults = {
 
186
                            'pricelist_id':self._get_pricelist(cr, uid, shop),
 
187
                            'shop_id': shop.id,
 
188
                            'picking_policy': shop.picking_policy,
 
189
                            'order_policy': shop.order_policy,
 
190
                            'invoice_quantity': shop.invoice_quantity
 
191
                        }
 
192
 
 
193
            if shop.is_tax_included:
 
194
                defaults.update({'price_type': 'tax_included'})
 
195
 
 
196
            self.import_shop_orders(cr, uid, shop, defaults, ctx)
 
197
        return False
 
198
 
 
199
    def import_shop_orders(self, cr, uid, shop, defaults, ctx):
 
200
        osv.except_osv(_("Not Implemented"), _("Not Implemented in abstract base module!"))
 
201
 
 
202
    def update_orders(self, cr, uid, ids, ctx):
 
203
        logger = netsvc.Logger()
 
204
        for shop in self.browse(cr, uid, ids):
 
205
            ctx['conn_obj'] = self.external_connection(cr, uid, shop.referential_id)
 
206
            #get all orders, which the state is not draft and the date of modification is superior to the last update, to exports
 
207
            req = "select ir_model_data.res_id, ir_model_data.name from sale_order inner join ir_model_data on sale_order.id = ir_model_data.res_id where ir_model_data.model='sale.order' and sale_order.shop_id=%s and ir_model_data.external_referential_id NOTNULL and sale_order.state != 'draft'"
 
208
            param = (shop.id,)
 
209
 
 
210
            if shop.last_update_order_export_date:
 
211
                req += "and sale_order.write_date > %s"
 
212
                param = (shop.id, shop.last_update_order_export_date)
 
213
 
 
214
            cr.execute(req, param)
 
215
            results = cr.fetchall()
 
216
 
 
217
            for result in results:
 
218
                ids = self.pool.get('sale.order').search(cr, uid, [('id', '=', result[0])])
 
219
                if ids:
 
220
                    id = ids[0]
 
221
                    order = self.pool.get('sale.order').browse(cr, uid, id, ctx)
 
222
                    order_ext_id = result[1].split('sale.order_')[1]
 
223
                    self.update_shop_orders(cr, uid, order, order_ext_id, ctx)
 
224
                    logger.notifyChannel('ext synchro', netsvc.LOG_INFO, "Successfully updated order with OpenERP id %s and ext id %s in external sale system" % (id, order_ext_id))
 
225
            self.pool.get('sale.shop').write(cr, uid, shop.id, {'last_update_order_export_date': time.strftime('%Y-%m-%d %H:%M:%S')})
 
226
        return False
 
227
 
 
228
 
 
229
    def update_shop_orders(self, cr, uid, order, ext_id, ctx):
 
230
        osv.except_osv(_("Not Implemented"), _("Not Implemented in abstract base module!"))
 
231
 
 
232
sale_shop()
 
233
 
 
234
 
 
235
class sale_order(osv.osv):
 
236
    _inherit = "sale.order"
 
237
 
 
238
 
 
239
    def payment_code_to_payment_settings(self, cr, uid, payment_code, ctx):
 
240
 
 
241
#        payment_setting_id = self.pool.get('base.sale.payment.type').search(cr, uid, [['name', 'ilike', payment_code]])[0]
 
242
        payment_setting_id = 1
 
243
        payment_setting_id and self.pool.get('base.sale.payment.type').browse(cr, uid, payment_setting_id, ctx) or False
 
244
 
 
245
    def generate_payment_with_pay_code(self, cr, uid, payment_code, partner_id, amount, payment_ref, entry_name, date, should_validate, ctx):
 
246
        payment_settings = self.payment_code_to_payment_settings(cr, uid, payment_code, ctx)
 
247
        if payment_settings:
 
248
            return self.generate_payment_with_journal(cr, uid, payment_settings.journal_id.id, partner_id, amount, payment_ref, entry_name, date, should_validate and payment_settings.validate_payment, ctx)
 
249
        return False
 
250
 
 
251
    def generate_payment_with_journal(self, cr, uid, journal_id, partner_id, amount, payment_ref, entry_name, date, should_validate, ctx):
 
252
        statement_vals = {
 
253
                            'name': 'ST_' + entry_name,
 
254
                            'journal_id': journal_id,
 
255
                            'balance_start': 0,
 
256
                            'balance_end_real': amount,
 
257
                            'date' : date
 
258
                        }
 
259
        statement_id = self.pool.get('account.bank.statement').create(cr, uid, statement_vals, ctx)
 
260
        statement = self.pool.get('account.bank.statement').browse(cr, uid, statement_id, ctx)
 
261
        account_id = self.pool.get('account.bank.statement.line').onchange_partner_id(cr, uid, False, partner_id, "customer", statement.currency.id, ctx)['value']['account_id']
 
262
        statement_line_vals = {
 
263
                                'statement_id': statement_id,
 
264
                                'name': entry_name,
 
265
                                'ref': payment_ref,
 
266
                                'amount': amount,
 
267
                                'partner_id': partner_id,
 
268
                                'account_id': account_id
 
269
                               }
 
270
        statement_line_id = self.pool.get('account.bank.statement.line').create(cr, uid, statement_line_vals, ctx)
 
271
        if should_validate:
 
272
            self.pool.get('account.bank.statement').button_confirm(cr, uid, [statement_id], ctx)
 
273
            self.pool.get('account.move.line').write(cr, uid, [statement.move_line_ids[0].id], {'date': date})
 
274
        return statement_line_id
 
275
 
 
276
sale_order()
 
277
 
 
278
#TODO deprecated remove!
 
279
class account_journal(osv.osv):
 
280
    _inherit = "account.journal"
 
281
 
 
282
    _columns = {
 
283
                'external_payment_codes': fields.char('External Payment Codes', size=256, help="Enter the external payment codes, comma separated. They will be used to select the payment journal."),
 
284
    }
 
285
 
 
286
account_journal()
 
287
 
 
288
 
 
289
 
 
290
class base_sale_payment_type(osv.osv):
 
291
    _name = "base.sale.payment.type"
 
292
    _description = "Base Sale Payment Type"
 
293
 
 
294
    _columns = {
 
295
                #TODO statement status, status invoice, picking, sale order,....
 
296
        'payment_type': fields.char('Name', size=64, required=True), # TODO multi payment name separed by ";"
 
297
        'picking_policy': fields.selection([('direct', 'Partial Delivery'), ('one', 'Complete Delivery')], 'Packing Policy'),
 
298
        'order_policy': fields.selection([
 
299
            ('prepaid', 'Payment Before Delivery'),
 
300
            ('manual', 'Shipping & Manual Invoice'),
 
301
            ('postpaid', 'Invoice on Order After Delivery'),
 
302
            ('picking', 'Invoice from the Packing'),
 
303
        ], 'Shipping Policy'),
 
304
        'invoice_quantity': fields.selection([('order', 'Ordered Quantities'), ('procurement', 'Shipped Quantities')], 'Invoice on'),
 
305
        'is_auto_reconcile': fields.boolean('Auto-reconcile?', help="if true will try to reconcile the order payment statement and the open invoice"),
 
306
        'validate_payment': fields.boolean('Validate Payment?'),
 
307
        'journal_id': fields.many2one('account.journal','Paiment Journal',required=True)
 
308
    }
 
309
 
 
310
base_sale_payment_type()