[MERGE] akretion's branches

bzr revid: qdp-launchpad@openerp.com-20120711105313-sizc97a5l1e66sa7
This commit is contained in:
Quentin (OpenERP) 2012-07-11 12:53:13 +02:00
commit b98b8a32e3
18 changed files with 1067 additions and 2097 deletions

View File

@ -1862,8 +1862,10 @@ class account_tax(osv.osv):
'applicable_type': fields.selection( [('true','Always'), ('code','Given by Python Code')], 'Applicability', required=True,
help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
'domain':fields.char('Domain', size=32, help="This field is only used if you develop your own module allowing developers to create specific taxes in a custom domain."),
'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account'),
'account_paid_id':fields.many2one('account.account', 'Refund Tax Account'),
'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account', help="Set the account that will be set by default on invoice tax lines for invoices. Leave empty to use the expense account."),
'account_paid_id':fields.many2one('account.account', 'Refund Tax Account', help="Set the account that will be set by default on invoice tax lines for refunds. Leave empty to use the expense account."),
'account_analytic_collected_id':fields.many2one('account.analytic.account', 'Invoice Tax Analytic Account', help="Set the analytic account that will be used by default on the invoice tax lines for invoices. Leave empty if you don't want to use an analytic account on the invoice tax lines by default."),
'account_analytic_paid_id':fields.many2one('account.analytic.account', 'Refund Tax Analytic Account', help="Set the analytic account that will be used by default on the invoice tax lines for refunds. Leave empty if you don't want to use an analytic account on the invoice tax lines by default."),
'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
'child_depend':fields.boolean('Tax on Children', help="Set if the tax computation is based on the computation of child taxes rather than on the total amount."),
@ -2001,6 +2003,8 @@ class account_tax(osv.osv):
'name':tax.description and tax.description + " - " + tax.name or tax.name,
'account_collected_id':tax.account_collected_id.id,
'account_paid_id':tax.account_paid_id.id,
'account_analytic_collected_id': tax.account_analytic_collected_id.id,
'account_analytic_paid_id': tax.account_analytic_paid_id.id,
'base_code_id': tax.base_code_id.id,
'ref_base_code_id': tax.ref_base_code_id.id,
'sequence': tax.sequence,
@ -2066,7 +2070,20 @@ class account_tax(osv.osv):
'taxes': [] # List of taxes, see compute for the format
}
"""
# By default, for each tax, tax amount will first be computed
# and rounded at the 'Account' decimal precision for each
# PO/SO/invoice line and then these rounded amounts will be
# summed, leading to the total amount for that tax. But, if the
# company has tax_calculation_rounding_method = round_globally,
# we still follow the same method, but we use a much larger
# precision when we round the tax amount for each line (we use
# the 'Account' decimal precision + 5), and that way it's like
# rounding after the sum of the tax amounts of each line
precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
tax_compute_precision = precision
if taxes and taxes[0].company_id.tax_calculation_rounding_method == 'round_globally':
tax_compute_precision += 5
totalin = totalex = round(price_unit * quantity, precision)
tin = []
tex = []
@ -2075,7 +2092,7 @@ class account_tax(osv.osv):
tex.append(tax)
else:
tin.append(tax)
tin = self.compute_inv(cr, uid, tin, price_unit, quantity, product=product, partner=partner)
tin = self.compute_inv(cr, uid, tin, price_unit, quantity, product=product, partner=partner, precision=tax_compute_precision)
for r in tin:
totalex -= r.get('amount', 0.0)
totlex_qty = 0.0
@ -2083,7 +2100,7 @@ class account_tax(osv.osv):
totlex_qty = totalex/quantity
except:
pass
tex = self._compute(cr, uid, tex, totlex_qty, quantity,product=product, partner=partner)
tex = self._compute(cr, uid, tex, totlex_qty, quantity, product=product, partner=partner, precision=tax_compute_precision)
for r in tex:
totalin += r.get('amount', 0.0)
return {
@ -2096,7 +2113,7 @@ class account_tax(osv.osv):
_logger.warning("Deprecated, use compute_all(...)['taxes'] instead of compute(...) to manage prices with tax included")
return self._compute(cr, uid, taxes, price_unit, quantity, product, partner)
def _compute(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None):
def _compute(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None, precision=None):
"""
Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
@ -2105,14 +2122,15 @@ class account_tax(osv.osv):
tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
one tax for each tax id in IDS and their children
"""
if not precision:
precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
res = self._unit_compute(cr, uid, taxes, price_unit, product, partner, quantity)
total = 0.0
precision_pool = self.pool.get('decimal.precision')
for r in res:
if r.get('balance',False):
r['amount'] = round(r.get('balance', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account')) - total
r['amount'] = round(r.get('balance', 0.0) * quantity, precision) - total
else:
r['amount'] = round(r.get('amount', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account'))
r['amount'] = round(r.get('amount', 0.0) * quantity, precision)
total += r['amount']
return res
@ -2160,6 +2178,8 @@ class account_tax(osv.osv):
'amount': amount,
'account_collected_id': tax.account_collected_id.id,
'account_paid_id': tax.account_paid_id.id,
'account_analytic_collected_id': tax.account_analytic_collected_id.id,
'account_analytic_paid_id': tax.account_analytic_paid_id.id,
'base_code_id': tax.base_code_id.id,
'ref_base_code_id': tax.ref_base_code_id.id,
'sequence': tax.sequence,
@ -2188,7 +2208,7 @@ class account_tax(osv.osv):
r['todo'] = 0
return res
def compute_inv(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None):
def compute_inv(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None, precision=None):
"""
Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
Price Unit is a VAT included price
@ -2198,15 +2218,15 @@ class account_tax(osv.osv):
tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
one tax for each tax id in IDS and their children
"""
if not precision:
precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
res = self._unit_compute_inv(cr, uid, taxes, price_unit, product, partner=None)
total = 0.0
obj_precision = self.pool.get('decimal.precision')
for r in res:
prec = obj_precision.precision_get(cr, uid, 'Account')
if r.get('balance',False):
r['amount'] = round(r['balance'] * quantity, prec) - total
r['amount'] = round(r['balance'] * quantity, precision) - total
else:
r['amount'] = round(r['amount'] * quantity, prec)
r['amount'] = round(r['amount'] * quantity, precision)
total += r['amount']
return res

View File

@ -43,6 +43,12 @@ class bank(osv.osv):
"Return the name to use when creating a bank journal"
return (bank.bank_name or '') + ' ' + bank.acc_number
def _prepare_name_get(self, cr, uid, bank_type_obj, bank_obj, context=None):
"""Add ability to have %(currency_name)s in the format_layout of
res.partner.bank.type"""
bank_obj._data[bank_obj.id]['currency_name'] = bank_obj.currency_id and bank_obj.currency_id.name or ''
return super(bank, self)._prepare_name_get(cr, uid, bank_type_obj, bank_obj, context=context)
def post_write(self, cr, uid, ids, context={}):
if isinstance(ids, (int, long)):
ids = [ids]

View File

@ -756,7 +756,7 @@ class account_invoice(osv.osv):
for tax in inv.tax_line:
if tax.manual:
continue
key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id, tax.account_analytic_id.id)
tax_key.append(key)
if not key in compute_taxes:
raise osv.except_osv(_('Warning !'), _('Global taxes defined, but they are not in invoice lines !'))
@ -1002,7 +1002,7 @@ class account_invoice(osv.osv):
'quantity': x.get('quantity',1.00),
'product_id': x.get('product_id', False),
'product_uom_id': x.get('uos_id', False),
'analytic_account_id': x.get('account_analytic_id', False),
'analytic_account_id': x.get('analytic_account_id', False),
}
def action_number(self, cr, uid, ids, context=None):
@ -1578,6 +1578,7 @@ class account_invoice_tax(osv.osv):
'invoice_id': fields.many2one('account.invoice', 'Invoice Line', ondelete='cascade', select=True),
'name': fields.char('Tax Description', size=64, required=True),
'account_id': fields.many2one('account.account', 'Tax Account', required=True, domain=[('type','<>','view'),('type','<>','income'), ('type', '<>', 'closed')]),
'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account'),
'base': fields.float('Base', digits_compute=dp.get_precision('Account')),
'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')),
'manual': fields.boolean('Manual'),
@ -1648,14 +1649,16 @@ class account_invoice_tax(osv.osv):
val['base_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['base'] * tax['base_sign'], context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
val['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['amount'] * tax['tax_sign'], context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
val['account_id'] = tax['account_collected_id'] or line.account_id.id
val['account_analytic_id'] = tax['account_analytic_collected_id']
else:
val['base_code_id'] = tax['ref_base_code_id']
val['tax_code_id'] = tax['ref_tax_code_id']
val['base_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['base'] * tax['ref_base_sign'], context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
val['tax_amount'] = cur_obj.compute(cr, uid, inv.currency_id.id, company_currency, val['amount'] * tax['ref_tax_sign'], context={'date': inv.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
val['account_id'] = tax['account_paid_id'] or line.account_id.id
val['account_analytic_id'] = tax['account_analytic_paid_id']
key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
key = (val['tax_code_id'], val['base_code_id'], val['account_id'], val['account_analytic_id'])
if not key in tax_grouped:
tax_grouped[key] = val
else:
@ -1687,7 +1690,8 @@ class account_invoice_tax(osv.osv):
'price': t['amount'] or 0.0,
'account_id': t['account_id'],
'tax_code_id': t['tax_code_id'],
'tax_amount': t['tax_amount']
'tax_amount': t['tax_amount'],
'analytic_account_id': t['account_analytic_id'],
})
return res

View File

@ -98,6 +98,7 @@
<field name="name"/>
<field name="sequence"/>
<field name="account_id" groups="account.group_account_user"/>
<field name="account_analytic_id" domain="[('type','&lt;&gt;','view'), ('company_id', '=', parent.company_id), ('parent_id', '!=', False)]" groups="analytic.group_analytic_accounting"/>
<field name="manual"/>
<field name="amount"/>
<field name="base" readonly="0"/>
@ -215,6 +216,7 @@
<tree editable="bottom" string="Taxes">
<field name="name"/>
<field name="account_id" groups="account.group_account_invoice"/>
<field name="account_analytic_id" domain="[('type','&lt;&gt;','view'), ('company_id', '=', parent.company_id), ('parent_id', '!=', False)]" groups="analytic.group_analytic_accounting"/>
<field name="base" on_change="base_change(base,parent.currency_id,parent.company_id,parent.date_invoice)" readonly="1"/>
<field name="amount" on_change="amount_change(amount,parent.currency_id,parent.company_id,parent.date_invoice)"/>

View File

@ -909,9 +909,9 @@
<field name="amount" attrs="{'readonly':[('type','in',('none', 'code', 'balance'))]}"/>
<separator colspan="4" string="Accounting Information"/>
<field name="account_collected_id" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','consolidation')]"/>
<label colspan="2" nolabel="1" string="Keep empty to use the income account"/>
<field name="account_analytic_collected_id" domain="[('type','&lt;&gt;','view'), ('company_id', '=', company_id), ('parent_id', '&lt;&gt;', False)]" groups="analytic.group_analytic_accounting"/>
<field name="account_paid_id" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','consolidation')]"/>
<label colspan="2" nolabel="1" string="Keep empty to use the expense account"/>
<field name="account_analytic_paid_id" domain="[('type','&lt;&gt;','view'), ('company_id', '=', company_id), ('parent_id', '&lt;&gt;', False)]" groups="analytic.group_analytic_accounting"/>
<separator colspan="4" string="Tax Declaration: Invoices"/>
<field name="base_code_id"/>
<field name="base_sign"/>

View File

@ -25,6 +25,11 @@ class res_company(osv.osv):
_inherit = "res.company"
_columns = {
'expects_chart_of_accounts': fields.boolean('Expects a Chart of Accounts'),
'tax_calculation_rounding_method': fields.selection([
('round_per_line', 'Round per Line'),
('round_globally', 'Round Globally'),
], 'Tax Calculation Rounding Method',
help="If you select 'Round per Line' : for each tax, the tax amount will first be computed and rounded for each PO/SO/invoice line and then these rounded amounts will be summed, leading to the total amount for that tax. If you select 'Round Globally': for each tax, the tax amount will be computed for each PO/SO/invoice line, then these amounts will be summed and eventually this total tax amount will be rounded. If you sell with tax included, you should choose 'Round per line' because you certainly want the sum of your tax-included line subtotals to be equal to the total amount with taxes."),
'paypal_account': fields.char("Paypal Account", size=128, help="Paypal username (usually email) for receiving online payments."),
'overdue_msg': fields.text('Overdue Payments Message', translate=True),
'property_reserve_and_surplus_account': fields.property(
@ -39,6 +44,7 @@ class res_company(osv.osv):
_defaults = {
'expects_chart_of_accounts': True,
'tax_calculation_rounding_method': 'round_per_line',
'overdue_msg': '''Dear Sir, dear Madam,
Our records indicate that some payments on your account are still due. Please find details below.

View File

@ -24,6 +24,7 @@
<field name="arch" type="xml">
<field name="currency_id" position="after">
<field name="property_reserve_and_surplus_account" colspan="2"/>
<field name="tax_calculation_rounding_method"/>
<field name="paypal_account" placeholder="sales@openerp.com"/>
</field>
</field>

View File

@ -49,6 +49,12 @@ class account_config_settings(osv.osv_memory):
'has_chart_of_accounts': fields.boolean('Company has a chart of accounts'),
'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', domain="[('visible','=', True)]"),
'code_digits': fields.integer('# of Digits', help="No. of Digits to use for account code"),
'tax_calculation_rounding_method': fields.related('company_id',
'tax_calculation_rounding_method', type='selection', selection=[
('round_per_line', 'Round per Line'),
('round_globally', 'Round Globally'),
], string='Tax Calculation Rounding Method',
help="If you select 'Round per Line' : for each tax, the tax amount will first be computed and rounded for each PO/SO/invoice line and then these rounded amounts will be summed, leading to the total amount for that tax. If you select 'Round Globally': for each tax, the tax amount will be computed for each PO/SO/invoice line, then these amounts will be summed and eventually this total tax amount will be rounded. If you sell with tax included, you should choose 'Round per line' because you certainly want the sum of your tax-included line subtotals to be equal to the total amount with taxes."),
'sale_tax': fields.many2one("account.tax.template", "Default Sale Tax"),
'purchase_tax': fields.many2one("account.tax.template", "Default Purchase Tax"),
'sale_tax_rate': fields.float('Sales Tax (%)'),
@ -152,6 +158,7 @@ class account_config_settings(osv.osv_memory):
'has_chart_of_accounts': has_chart_of_accounts,
'has_fiscal_year': bool(fiscalyear_count),
'chart_template_id': False,
'tax_calculation_rounding_method': company.tax_calculation_rounding_method,
}
# update journals and sequences
for journal_type in ('sale', 'sale_refund', 'purchase', 'purchase_refund'):

View File

@ -76,6 +76,7 @@
<group>
<field name="default_purchase_tax" domain="[('type_tax_use','=','purchase'), ('company_id','=',company_id)]"
attrs="{'invisible': [('has_chart_of_accounts','=',False)]}"/>
<field name="tax_calculation_rounding_method"/>
<field name="module_account_asset"/>
<field name="module_account_budget"/>
</group>

View File

@ -309,6 +309,10 @@ class res_users(osv.osv):
return user_id
_columns = {
'employee_ids': fields.one2many('hr.employee', 'user_id', 'Related employees'),
}
res_users()

File diff suppressed because it is too large Load Diff

View File

@ -155,7 +155,6 @@ class product_pricelist(osv.osv):
currency_obj = self.pool.get('res.currency')
product_obj = self.pool.get('product.product')
product_template_obj = self.pool.get('product.template')
product_category_obj = self.pool.get('product.category')
product_uom_obj = self.pool.get('product.uom')
supplierinfo_obj = self.pool.get('product.supplierinfo')
@ -246,7 +245,7 @@ class product_pricelist(osv.osv):
price = 0.0
if sinfo:
qty_in_product_uom = qty
product_default_uom = product_template_obj.read(cr, uid, [tmpl_id], ['uom_id'])[0]['uom_id'][0]
product_default_uom = product_obj.read(cr, uid, [product_id], ['uom_id'])[0]['uom_id'][0]
supplier = supplierinfo_obj.browse(cr, uid, sinfo, context=context)[0]
seller_uom = supplier.product_uom and supplier.product_uom.id or False
if seller_uom and product_default_uom and product_default_uom != seller_uom:

View File

@ -559,8 +559,8 @@ class product_product(osv.osv):
return False
def _check_ean_key(self, cr, uid, ids, context=None):
for product in self.browse(cr, uid, ids, context=context):
res = check_ean(product.ean13)
for product in self.read(cr, uid, ids, ['ean13'], context=context):
res = check_ean(product['ean13'])
return res
_constraints = [(_check_ean_key, 'Error: Invalid ean code', ['ean13'])]

View File

@ -47,7 +47,6 @@ class sale_order_line(osv.osv):
item_obj = self.pool.get('product.pricelist.item')
price_type_obj = self.pool.get('product.price.type')
product_obj = self.pool.get('product.product')
template_obj = self.pool.get('product.template')
field_name = 'list_price'
if res_dict.get('item_id',False) and res_dict['item_id'].get(pricelist,False):
@ -57,9 +56,7 @@ class sale_order_line(osv.osv):
field_name = price_type_obj.browse(cr, uid, item_base).field
product = product_obj.browse(cr, uid, product_id, context)
product_tmpl_id = product.product_tmpl_id.id
product_read = template_obj.read(cr, uid, product_tmpl_id, [field_name], context)
product_read = product_obj.read(cr, uid, product_id, [field_name], context=context)
factor = 1.0
if uom and uom != product.uom_id.id:
@ -110,7 +107,6 @@ class account_invoice_line(osv.osv):
item_obj = self.pool.get('product.pricelist.item')
price_type_obj = self.pool.get('product.price.type')
product_obj = self.pool.get('product.product')
template_obj = self.pool.get('product.template')
field_name = 'list_price'
if res_dict.get('item_id',False) and res_dict['item_id'].get(pricelist,False):
@ -120,9 +116,7 @@ class account_invoice_line(osv.osv):
field_name = price_type_obj.browse(cr, uid, item_base).field
product = product_obj.browse(cr, uid, product_id, context)
product_tmpl_id = product.product_tmpl_id.id
product_read = template_obj.read(cr, uid, product_tmpl_id, [field_name], context)
product_read = product_obj.read(cr, uid, product_id, [field_name], context=context)
factor = 1.0
if uom and uom != product.uom_id.id:

View File

@ -243,13 +243,16 @@ class product_product(osv.osv):
child_location_ids = location_obj.search(cr, uid, [('location_id', 'child_of', location_ids)])
location_ids = child_location_ids or location_ids
# this will be a dictionary of the UoM resources we need for conversion purposes, by UoM id
uoms_o = {}
# this will be a dictionary of the product UoM by product id
product2uom = {}
for product in self.browse(cr, uid, ids, context=context):
product2uom[product.id] = product.uom_id.id
uoms_o[product.uom_id.id] = product.uom_id
uom_ids = []
for product in self.read(cr, uid, ids, ['uom_id'], context=context):
product2uom[product['id']] = product['uom_id'][0]
uom_ids.append(product['uom_id'][0])
# this will be a dictionary of the UoM resources we need for conversion purposes, by UoM id
uoms_o = {}
for uom in self.pool.get('product.uom').browse(cr, uid, uom_ids, context=context):
uoms_o[uom.id] = uom
results = []
results2 = []

View File

@ -103,20 +103,14 @@ class stock_change_product_qty(osv.osv_memory):
self.change_product_qty_send_note(cr, uid, [data.id], context)
return {}
def change_product_qty_send_note (self, cr, uid, ids, context=None):
def change_product_qty_send_note(self, cr, uid, ids, context=None):
prod_obj = self.pool.get('product.product')
location_obj = self.pool.get('stock.location')
prod_temp_obj = self.pool.get('product.template')
uom_obj = self.pool.get('product.uom')
for data in self.browse(cr, uid, ids, context=context):
for location in location_obj.browse(cr, uid, [data.location_id.id], context=context):
location_name = location.name
for prod in prod_obj.browse(cr, uid, [data.product_id.id], context=context):
for prod_temp in prod_temp_obj.browse(cr, uid, [prod.product_tmpl_id.id], context=context):
for uom in uom_obj.browse(cr, uid, [prod_temp.uom_id.id], context=context):
message = _("<b>Quantity has been changed</b> to <em>%s %s </em> for <em>%s</em> location.") % (data.new_quantity,uom.name,location_name)
prod_obj.message_append_note(cr, uid, [prod.id], body=message, context=context)
location_name = location_obj.browse(cr, uid, data.location_id.id, context=context).name
message = _("<b>Quantity has been changed</b> to <em>%s %s </em> for <em>%s</em> location.") % (data.new_quantity, data.product_id.uom_id.name, location_name)
prod_obj.message_append_note(cr, uid, [data.product_id.id], body=message, context=context)
stock_change_product_qty()

View File

@ -56,7 +56,6 @@ class stock_planning_createlines(osv.osv_memory):
planning_obj = self.pool.get('stock.planning')
mod_obj = self.pool.get('ir.model.data')
prod_categ_obj = self.pool.get('product.category')
template_obj = self.pool.get('product.template')
planning_lines = []
for f in self.browse(cr, uid, ids, context=context):
if f.forecasted_products:
@ -67,8 +66,7 @@ class stock_planning_createlines(osv.osv_memory):
else:
categ_ids = f.product_categ_id.id and [f.product_categ_id.id] or []
prod_categ_ids = prod_categ_obj.search(cr,uid,[('parent_id','child_of',categ_ids)])
templates_ids = template_obj.search(cr,uid,[('categ_id','in',prod_categ_ids)])
products_id1 = product_obj.search(cr,uid,[('product_tmpl_id','in',templates_ids)])
products_id1 = product_obj.search(cr,uid,[('categ_id','in',prod_categ_ids)])
if len(products_id1)==0:
raise osv.except_osv(_('Error !'), _('No forecasts for selected period or no products in selected category !'))

View File

@ -49,13 +49,11 @@ class stock_sale_forecast_createlines(osv.osv_memory):
forecast_obj = self.pool.get('stock.sale.forecast')
mod_obj = self.pool.get('ir.model.data')
prod_categ_obj = self.pool.get('product.category')
template_obj = self.pool.get('product.template')
forecast_lines = []
for f in self.browse(cr, uid, ids, context=context):
categ_ids = f.product_categ_id.id and [f.product_categ_id.id] or []
prod_categ_ids = prod_categ_obj.search(cr, uid, [('parent_id','child_of', categ_ids)])
templates_ids = template_obj.search(cr, uid, [('categ_id','in',prod_categ_ids)])
products_ids = product_obj.search(cr, uid, [('product_tmpl_id','in',templates_ids)])
products_ids = product_obj.search(cr, uid, [('categ_id','in',prod_categ_ids)])
if len(products_ids) == 0:
raise osv.except_osv(_('Error !'), _('No products in selected category !'))
copy = f.copy_forecast