1
# -*- encoding: utf-8 -*-
2
#########################################################################
4
#########################################################################
6
# Copyright (C) 2009 Raphaël Valyi #
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. #
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. #
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
#########################################################################
22
from osv import osv, fields
23
from base_external_referentials import external_osv
26
from tools.translate import _
27
#from datetime import datetime
31
class external_shop_group(external_osv.external_osv):
32
_name = 'external.shop.group'
33
_description = 'External Referential Shop Group'
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'),
44
class external_referential(osv.osv):
45
_inherit = 'external.referential'
48
'shop_group_ids': fields.one2many('external.shop.group', 'referential_id', 'Sub Entities'),
51
external_referential()
54
class product_category(osv.osv):
55
_inherit = "product.category"
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)
63
def _get_recursive_children_ids(self, cr, uid, ids, name, args, context=None):
65
for category in self.browse(cr, uid, ids):
66
res[category.id] = self.collect_children(category, [category.id])
70
'recursive_childen_ids': fields.function(_get_recursive_children_ids, method=True, type='one2many', relation="product.category", string='All Child Categories'),
76
class sale_shop(external_osv.external_osv):
77
_inherit = "sale.shop"
79
def _get_exportable_product_ids(self, cr, uid, ids, name, args, context=None):
81
for shop in self.browse(cr, uid, ids):
82
root_categories = [category for category in shop.exportable_root_category_ids]
84
for category in root_categories:
85
all_categories += [category.id for category in category.recursive_childen_ids]
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'))
90
product_ids = self.pool.get("product.product").search(cr, uid, ['|',('categ_id', 'in', all_categories),('categ_ids', 'in', all_categories)])
92
product_ids = self.pool.get("product.product").search(cr, uid, [('categ_id', 'in', all_categories)])
93
res[shop.id] = product_ids
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?"),
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',
133
def _get_pricelist(self, cr, uid, shop):
134
if shop.pricelist_id:
135
return shop.pricelist_id.id
137
return self.pool.get('product.pricelist').search(cr, uid, [('type', '=', 'sale'), ('active', '=', True)])[0]
139
def export_categories(self, cr, uid, shop, ctx):
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)
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)
152
def export_products(self, cr, uid, shop, ctx):
153
self.export_products_collection(cr, uid, shop, shop.exportable_product_ids, ctx)
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)
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)])
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')})
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!"))
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)
186
'pricelist_id':self._get_pricelist(cr, uid, shop),
188
'picking_policy': shop.picking_policy,
189
'order_policy': shop.order_policy,
190
'invoice_quantity': shop.invoice_quantity
193
if shop.is_tax_included:
194
defaults.update({'price_type': 'tax_included'})
196
self.import_shop_orders(cr, uid, shop, defaults, ctx)
199
def import_shop_orders(self, cr, uid, shop, defaults, ctx):
200
osv.except_osv(_("Not Implemented"), _("Not Implemented in abstract base module!"))
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'"
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)
214
cr.execute(req, param)
215
results = cr.fetchall()
217
for result in results:
218
ids = self.pool.get('sale.order').search(cr, uid, [('id', '=', result[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')})
229
def update_shop_orders(self, cr, uid, order, ext_id, ctx):
230
osv.except_osv(_("Not Implemented"), _("Not Implemented in abstract base module!"))
235
class sale_order(osv.osv):
236
_inherit = "sale.order"
239
def payment_code_to_payment_settings(self, cr, uid, payment_code, ctx):
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
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)
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)
251
def generate_payment_with_journal(self, cr, uid, journal_id, partner_id, amount, payment_ref, entry_name, date, should_validate, ctx):
253
'name': 'ST_' + entry_name,
254
'journal_id': journal_id,
256
'balance_end_real': amount,
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,
267
'partner_id': partner_id,
268
'account_id': account_id
270
statement_line_id = self.pool.get('account.bank.statement.line').create(cr, uid, statement_line_vals, ctx)
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
278
#TODO deprecated remove!
279
class account_journal(osv.osv):
280
_inherit = "account.journal"
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."),
290
class base_sale_payment_type(osv.osv):
291
_name = "base.sale.payment.type"
292
_description = "Base Sale Payment Type"
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)
310
base_sale_payment_type()