[MERGE] forwardport of bugfixes made in v7 up to revision 8854.
authorQuentin (OpenERP) <qdp-launchpad@openerp.com>
Thu, 14 Mar 2013 11:27:40 +0000 (12:27 +0100)
committerQuentin (OpenERP) <qdp-launchpad@openerp.com>
Thu, 14 Mar 2013 11:27:40 +0000 (12:27 +0100)
bzr revid: qdp-launchpad@openerp.com-20130314112740-s01t51p4m7mxcumz

32 files changed:
1  2 
addons/account/account_invoice.py
addons/account/account_invoice_view.xml
addons/account_analytic_analysis/account_analytic_analysis.py
addons/account_analytic_analysis/account_analytic_analysis_cron.xml
addons/account_analytic_analysis/account_analytic_analysis_view.xml
addons/account_followup/account_followup.py
addons/account_followup/account_followup_customers.xml
addons/account_followup/tests/test_account_followup.py
addons/analytic_contract_hr_expense/analytic_contract_hr_expense_view.xml
addons/crm/crm_lead.py
addons/hr_expense/__openerp__.py
addons/hr_expense/hr_expense.py
addons/hr_expense/hr_expense_view.xml
addons/hr_expense/hr_expense_workflow.xml
addons/hr_expense/test/expense_process.yml
addons/hr_recruitment/hr_recruitment.py
addons/lunch/lunch_view.xml
addons/mail/mail_thread.py
addons/mail/static/src/js/mail.js
addons/mail/wizard/invite.py
addons/mrp/mrp_view.xml
addons/point_of_sale/point_of_sale.py
addons/procurement/procurement.py
addons/procurement/procurement_view.xml
addons/product/product_view.xml
addons/project/project.py
addons/project/project_view.xml
addons/project_issue/project_issue_view.xml
addons/purchase/purchase_view.xml
addons/sale/sale_view.xml
addons/stock/stock.py
addons/stock/stock_view.xml

@@@ -22,7 -22,9 +22,8 @@@
  import time
  from lxml import etree
  import openerp.addons.decimal_precision as dp
+ import openerp.exceptions
  
 -from openerp import netsvc
  from openerp import pooler
  from openerp.osv import fields, osv, orm
  from openerp.tools.translate import _
@@@ -634,86 -544,6 +634,97 @@@ class account_analytic_account(osv.osv)
              pass
          return result
  
 +    def _prepare_invoice(self, cr, uid, contract, context=None):
 +        context = context or {}
 +
 +        inv_obj = self.pool.get('account.invoice')
 +        journal_obj = self.pool.get('account.journal')
 +        fpos_obj = self.pool.get('account.fiscal.position')
 +
 +        if not contract.partner_id:
 +            raise osv.except_osv(_('No Customer Defined !'),_("You must first select a Customer for Contract %s!") % contract.name )
 +
 +        fpos = contract.partner_id.property_account_position.id or False
 +        journal_ids = journal_obj.search(cr, uid, [('type', '=','sale'),('company_id', '=', contract.company_id.id or False)], limit=1)
 +        if not journal_ids:
 +            raise osv.except_osv(_('Error!'),
 +            _('Please define a sale journal for the company "%s".') % (contract.company_id.name or '', ))
 +
 +        partner_payment_term = contract.partner_id.property_payment_term and contract.partner_id.property_payment_term.id or False
 +
 +
 +        inv_data = {
 +           'reference': contract.code or False,
 +           'account_id': contract.partner_id.property_account_receivable.id,
 +           'type': 'out_invoice',
 +           'partner_id': contract.partner_id.id,
 +           'currency_id': contract.partner_id.property_product_pricelist.id or False,
 +           'journal_id': len(journal_ids) and journal_ids[0] or False,
 +           'date_invoice': contract.recurring_next_date,
 +           'origin': contract.name,
 +           'fiscal_position': fpos,
 +           'payment_term': partner_payment_term,
 +           'company_id': contract.company_id.id or False,
 +        }
 +        invoice_id = inv_obj.create(cr, uid, inv_data, context=context)
 +
 +        for line in contract.recurring_invoice_line_ids:
 +
 +            res = line.product_id
 +            account_id = res.property_account_income.id
 +            if not account_id:
 +                account_id = res.categ_id.property_account_income_categ.id
 +            account_id = fpos_obj.map_account(cr, uid, fpos, account_id)
 +
 +            taxes = res.taxes_id and res.taxes_id or False
 +            tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
 +
 +            invoice_line_vals = {
 +                'name': line.name,
 +                'account_id': account_id,
 +                'account_analytic_id': contract.id,
 +                'price_unit': line.price_unit or 0.0,
 +                'quantity': line.quantity,
 +                'uos_id': line.uom_id.id or False,
 +                'product_id': line.product_id.id or False,
 +                'invoice_id' : invoice_id,
 +                'invoice_line_tax_id': [(6, 0, tax_id)],
 +            }
 +            self.pool.get('account.invoice.line').create(cr, uid, invoice_line_vals, context=context)
 +
 +        inv_obj.button_compute(cr, uid, [invoice_id], context=context)
 +        return invoice_id
 +
 +    def recurring_create_invoice(self, cr, uid, automatic=False, context=None):
 +        context = context or {}
 +        current_date =  time.strftime('%Y-%m-%d')
 +
 +        contract_ids = self.search(cr, uid, [('recurring_next_date','<=', current_date), ('state','=', 'open'), ('recurring_invoices','=', True)])
 +        for contract in self.browse(cr, uid, contract_ids, context=context):
 +            invoice_id = self._prepare_invoice(cr, uid, contract, context=context)
 +
 +            next_date = datetime.datetime.strptime(contract.recurring_next_date or current_date, "%Y-%m-%d")
 +            interval = contract.recurring_interval
 +            if contract.recurring_rule_type == 'daily':
 +                new_date = next_date+relativedelta(days=+interval)
 +            elif contract.recurring_rule_type == 'weekly':
 +                new_date = next_date+relativedelta(weeks=+interval)
 +            else:
 +                new_date = next_date+relativedelta(months=+interval)
 +            self.write(cr, uid, [contract.id], {'recurring_next_date': new_date.strftime('%Y-%m-%d')}, context=context)
 +        return True
 +
++    def onchange_invoice_on_timesheets(self, cr, uid, ids, invoice_on_timesheets, context=None):
++        if not invoice_on_timesheets:
++            return {}
++        result = {'value': {'use_timesheets': True}}
++        try:
++            to_invoice = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'hr_timesheet_invoice', 'timesheet_invoice_factor1')
++            result['value']['to_invoice'] = to_invoice[1]
++        except ValueError:
++            pass
++        return result
++
  class account_analytic_account_summary_user(osv.osv):
      _name = "account_analytic_analysis.summary.user"
      _description = "Hours Summary by User"
                              </td>
                          </tr>
                      </table>
 -                    <group name='invoice_on_timesheets' attrs="{'invisible': [('invoice_on_timesheets','=',False)]}">
 -                        <p class="oe_grey oe_edit_only" colspan="2">
 +                    <group name='invoice_on_timesheets'>
-                         <p class="oe_grey oe_edit_only" colspan="2" attrs="{'invisible': [('invoice_on_timesheets','=',False)]}">
-                             When invoicing on timesheet, OpenERP uses the
++                        <p name='invoice_on_timesheets_label' class="oe_grey oe_edit_only" colspan="2" attrs="{'invisible': [('invoice_on_timesheets','=',False)]}">
+                             When reinvoicing costs, OpenERP uses the
                              pricelist of the contract which uses the price
-                             defined on the product related to each employee to
-                             define the customer invoice price rate.
+                             defined on the product related (e.g timesheet 
+                             products are defined on each employee). 
                          </p>
                          <group>
                              <field name="pricelist_id"
                          </td>
                      </tr>
                  </xpath>
 -                <xpath expr="//group[@name='invoice_on_timesheets']" position="attributes">
++                <xpath expr="//p[@name='invoice_on_timesheets_label']" position="attributes">
+                     <attribute name="attrs">{'invisible': [('invoice_on_timesheets','=',False),('charge_expenses','=',False)]}</attribute>
+                 </xpath>
+                 <xpath expr="//field[@name='pricelist_id']" position="attributes">
 -                    <attribute name="attrs">{'required': ['|',('invoice_on_timesheets','=',True),('charge_expenses','=',True)]}</attribute>
++                    <attribute name="attrs">{'required': ['|',('invoice_on_timesheets','=',True),('charge_expenses','=',True)], 'invisible':[('invoice_on_timesheets','=',False), ('charge_expenses','=',False)]}</attribute>
+                 </xpath>
+                 <xpath expr="//field[@name='to_invoice']" position="attributes">
+                     <attribute name="attrs">{'required': ['|',('invoice_on_timesheets','=',True),('charge_expenses','=',True)]}</attribute>
+                     <attribute name="string">Expenses and Timesheet Invoicing Ratio</attribute>
+                 </xpath>
              </field>
          </record>
          
Simple merge
@@@ -39,14 -39,14 +39,14 @@@ The whole flow is implemented as
  * Draft expense
  * Confirmation of the sheet by the employee
  * Validation by his manager
--* Validation by the accountant and receipt creation
++* Validation by the accountant and accounting entries creation
  
  This module also uses analytic accounting and is compatible with the invoice on timesheet module so that you are able to automatically re-invoice your customers' expenses if your work by project.
      """,
      'author': 'OpenERP SA',
      'website': 'http://www.openerp.com',
      'images': ['images/hr_expenses_analysis.jpeg', 'images/hr_expenses.jpeg'],
-     'depends': ['hr', 'account_voucher'],
 -    'depends': ['hr', 'account_voucher', 'account_accountant'],
++    'depends': ['hr', 'account_accountant'],
      'data': [
          'security/ir.model.access.csv',
          'hr_expense_data.xml',
@@@ -144,88 -145,230 +144,229 @@@ class hr_expense_expense(osv.osv)
      def expense_canceled(self, cr, uid, ids, context=None):
          return self.write(cr, uid, ids, {'state': 'cancelled'}, context=context)
  
-     def action_receipt_create(self, cr, uid, ids, context=None):
-         property_obj = self.pool.get('ir.property')
-         sequence_obj = self.pool.get('ir.sequence')
-         analytic_journal_obj = self.pool.get('account.analytic.journal')
-         account_journal = self.pool.get('account.journal')
-         voucher_obj = self.pool.get('account.voucher')
-         currency_obj = self.pool.get('res.currency')
+     def account_move_get(self, cr, uid, expense_id, context=None):
+         '''
+         This method prepare the creation of the account move related to the given expense.
+         :param expense_id: Id of voucher for which we are creating account_move.
+         :return: mapping between fieldname and value of account move to create
+         :rtype: dict
+         '''
+         journal_obj = self.pool.get('account.journal')
+         expense = self.browse(cr, uid, expense_id, context=context)
+         company_id = expense.company_id.id
+         date = expense.date_confirm
+         ref = expense.name
+         journal_id = False
+         if expense.journal_id:
+             journal_id = expense.journal_id.id
+         else:
+             journal_id = journal_obj.search(cr, uid, [('type', '=', 'purchase'), ('company_id', '=', company_id)])
+             if not journal_id:
+                 raise osv.except_osv(_('Error!'), _("No expense journal found. Please make sure you have a journal with type 'purchase' configured."))
+             journal_id = journal_id[0]
+         return self.pool.get('account.move').account_move_prepare(cr, uid, journal_id, date=date, ref=ref, company_id=company_id, context=context)
+     def line_get_convert(self, cr, uid, x, part, date, context=None):
+         partner_id  = self.pool.get('res.partner')._find_accounting_partner(part).id
+         return {
+             'date_maturity': x.get('date_maturity', False),
+             'partner_id': partner_id,
+             'name': x['name'][:64],
+             'date': date,
+             'debit': x['price']>0 and x['price'],
+             'credit': x['price']<0 and -x['price'],
+             'account_id': x['account_id'],
+             'analytic_lines': x.get('analytic_lines', False),
+             'amount_currency': x['price']>0 and abs(x.get('amount_currency', False)) or -abs(x.get('amount_currency', False)),
+             'currency_id': x.get('currency_id', False),
+             'tax_code_id': x.get('tax_code_id', False),
+             'tax_amount': x.get('tax_amount', False),
+             'ref': x.get('ref', False),
+             '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),
+         }
+     def compute_expense_totals(self, cr, uid, exp, company_currency, ref, account_move_lines, context=None):
+         '''
+         internal method used for computation of total amount of an expense in the company currency and
+         in the expense currency, given the account_move_lines that will be created. It also do some small
+         transformations at these account_move_lines (for multi-currency purposes)
+         
+         :param account_move_lines: list of dict
+         :rtype: tuple of 3 elements (a, b ,c)
+             a: total in company currency
+             b: total in hr.expense currency
+             c: account_move_lines potentially modified
+         '''
+         cur_obj = self.pool.get('res.currency')
          if context is None:
-             context = {}
-         for exp in self.browse(cr, uid, ids, context=context):
-             company_id = exp.company_id.id
-             lines = []
-             total = 0.0
-             ctx = context.copy()
-             ctx.update({'date': exp.date})
-             journal = False
-             if exp.journal_id:
-                 journal = exp.journal_id
+             context={}
+         context.update({'date': exp.date_confirm or time.strftime('%Y-%m-%d')})
+         total = 0.0
+         total_currency = 0.0
+         for i in account_move_lines:
+             if exp.currency_id.id != company_currency:
+                 i['currency_id'] = exp.currency_id.id
+                 i['amount_currency'] = i['price']
+                 i['price'] = cur_obj.compute(cr, uid, exp.currency_id.id,
+                         company_currency, i['price'],
+                         context=context)
              else:
-                 journal_id = voucher_obj._get_journal(cr, uid, context={'type': 'purchase', 'company_id': company_id})
-                 if journal_id:
-                     journal = account_journal.browse(cr, uid, journal_id, context=context)
-             if not journal:
-                raise osv.except_osv(_('Error!'), _("No expense journal found. Please make sure you have a journal with type 'purchase' configured."))
-             for line in exp.line_ids:
-                 if line.product_id:
-                     acc = line.product_id.property_account_expense
-                     if not acc:
-                         acc = line.product_id.categ_id.property_account_expense_categ
-                 else:
-                     acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company_id})
-                     if not acc:
-                         raise osv.except_osv(_('Error!'), _('Please configure Default Expense account for Product purchase: `property_account_expense_categ`.'))
-                 total_amount = line.total_amount
-                 if journal.currency:
-                     if exp.currency_id != journal.currency:
-                         total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, journal.currency.id, total_amount, context=ctx)
-                 elif exp.currency_id != exp.company_id.currency_id:
-                     total_amount = currency_obj.compute(cr, uid, exp.currency_id.id, exp.company_id.currency_id.id, total_amount, context=ctx)
-                 lines.append((0, False, {
-                     'name': line.name,
-                     'account_id': acc.id,
-                     'account_analytic_id': line.analytic_account.id,
-                     'amount': total_amount,
-                     'type': 'dr'
-                 }))
-                 total += total_amount
+                 i['amount_currency'] = False
+                 i['currency_id'] = False
+             total -= i['price']
+             total_currency -= i['amount_currency'] or i['price']
+         return total, total_currency, account_move_lines
 -
 -
 -    def action_receipt_create(self, cr, uid, ids, context=None):
++        
++    def action_move_create(self, cr, uid, ids, context=None):
+         '''
+         main function that is called when trying to create the accounting entries related to an expense
+         '''
+         move_obj = self.pool.get('account.move')
+         for exp in self.browse(cr, uid, ids, context=context):
              if not exp.employee_id.address_home_id:
                  raise osv.except_osv(_('Error!'), _('The employee must have a home address.'))
+             company_currency = exp.company_id.currency_id.id
+             diff_currency_p = exp.currency_id.id <> company_currency
+             
+             #create the move that will contain the accounting entries
+             move_id = move_obj.create(cr, uid, self.account_move_get(cr, uid, exp.id, context=context), context=context)
+         
+             #one account.move.line per expense line (+taxes..)
+             eml = self.move_line_get(cr, uid, exp.id, context=context)
+             
+             #create one more move line, a counterline for the total on payable account
+             total, total_currency, eml = self.compute_expense_totals(cr, uid, exp, company_currency, exp.name, eml, context=context)
              acc = exp.employee_id.address_home_id.property_account_payable.id
-             voucher = {
-                 'name': exp.name or '/',
-                 'reference': sequence_obj.get(cr, uid, 'hr.expense.invoice'),
-                 'account_id': acc,
-                 'type': 'purchase',
-                 'partner_id': exp.employee_id.address_home_id.id,
-                 'company_id': company_id,
-                 'line_ids': lines,
-                 'amount': total,
-                 'journal_id': journal.id,
-             }
-             if journal and not journal.analytic_journal_id:
-                 analytic_journal_ids = analytic_journal_obj.search(cr, uid, [('type','=','purchase')], context=context)
-                 if analytic_journal_ids:
-                     account_journal.write(cr, uid, [journal.id], {'analytic_journal_id': analytic_journal_ids[0]}, context=context)
-             voucher_id = voucher_obj.create(cr, uid, voucher, context=context)
-             self.write(cr, uid, [exp.id], {'voucher_id': voucher_id, 'state': 'done'}, context=context)
+             eml.append({
+                     'type': 'dest',
+                     'name': '/',
+                     'price': total, 
+                     'account_id': acc, 
+                     'date_maturity': exp.date_confirm, 
+                     'amount_currency': diff_currency_p and total_currency or False, 
+                     'currency_id': diff_currency_p and exp.currency_id.id or False, 
+                     'ref': exp.name
+                     })
+             #convert eml into an osv-valid format
+             lines = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, exp.employee_id.address_home_id, exp.date_confirm, context=context)), eml)
+             move_obj.write(cr, uid, [move_id], {'line_id': lines}, context=context)
+             self.write(cr, uid, ids, {'account_move_id': move_id, 'state': 'done'}, context=context)
          return True
-     
-     def action_view_receipt(self, cr, uid, ids, context=None):
+     def move_line_get(self, cr, uid, expense_id, context=None):
+         res = []
+         tax_obj = self.pool.get('account.tax')
+         cur_obj = self.pool.get('res.currency')
+         if context is None:
+             context = {}
+         exp = self.browse(cr, uid, expense_id, context=context)
+         company_currency = exp.company_id.currency_id.id
+         for line in exp.line_ids:
+             mres = self.move_line_get_item(cr, uid, line, context)
+             if not mres:
+                 continue
+             res.append(mres)
+             tax_code_found= False
+             
+             #Calculate tax according to default tax on product
+             
+             taxes = []
+             #Taken from product_id_onchange in account.invoice
+             if line.product_id:
+                 fposition_id = False
+                 fpos_obj = self.pool.get('account.fiscal.position')
+                 fpos = fposition_id and fpos_obj.browse(cr, uid, fposition_id, context=context) or False
+                 product = line.product_id
+                 taxes = product.supplier_taxes_id
+                 #If taxes are not related to the product, maybe they are in the account
+                 if not taxes:
+                     a = product.property_account_expense.id #Why is not there a check here?
+                     if not a:
+                         a = product.categ_id.property_account_expense_categ.id
+                     a = fpos_obj.map_account(cr, uid, fpos, a)
+                     taxes = a and self.pool.get('account.account').browse(cr, uid, a, context=context).tax_ids or False
+                 tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
+             if not taxes:
+                 continue
+             #Calculating tax on the line and creating move?
+             for tax in tax_obj.compute_all(cr, uid, taxes,
+                     line.unit_amount ,
+                     line.unit_quantity, line.product_id,
+                     exp.user_id.partner_id)['taxes']:
+                 tax_code_id = tax['base_code_id']
+                 tax_amount = line.total_amount * tax['base_sign']
+                 if tax_code_found:
+                     if not tax_code_id:
+                         continue
+                     res.append(self.move_line_get_item(cr, uid, line, context))
+                     res[-1]['price'] = 0.0
+                     res[-1]['account_analytic_id'] = False
+                 elif not tax_code_id:
+                     continue
+                 tax_code_found = True
+                 res[-1]['tax_code_id'] = tax_code_id
+                 res[-1]['tax_amount'] = cur_obj.compute(cr, uid, exp.currency_id.id, company_currency, tax_amount, context={'date': exp.date_confirm})
+                 
+                 #Will create the tax here as we don't have the access 
+                 assoc_tax = {
+                              'type':'tax',
+                              'name':tax['name'],
+                              'price_unit': tax['price_unit'],
+                              'quantity': 1,
+                              'price':  tax['amount'] * tax['base_sign'] or 0.0,
+                              'account_id': tax['account_collected_id'],
+                              'tax_code_id': tax['tax_code_id'],
+                              'tax_amount': tax['amount'] * tax['base_sign'],
+                              }
+                 res.append(assoc_tax)
+         return res
+     def move_line_get_item(self, cr, uid, line, context=None):
+         company = line.expense_id.company_id
+         property_obj = self.pool.get('ir.property')
+         if line.product_id:
+             acc = line.product_id.property_account_expense
+             if not acc:
+                 acc = line.product_id.categ_id.property_account_expense_categ
+         else:
+             acc = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context={'force_company': company.id})
+             if not acc:
+                 raise osv.except_osv(_('Error!'), _('Please configure Default Expense account for Product purchase: `property_account_expense_categ`.'))
+         return {
+             'type':'src',
+             'name': line.name.split('\n')[0][:64],
+             'price_unit':line.unit_amount,
+             'quantity':line.unit_quantity,
+             'price':line.total_amount,
+             'account_id':acc.id,
+             'product_id':line.product_id.id,
+             'uos_id':line.uom_id.id,
+             'account_analytic_id':line.analytic_account.id,
+         }
 -    def action_view_receipt(self, cr, uid, ids, context=None):
++    def action_view_move(self, cr, uid, ids, context=None):
          '''
-         This function returns an action that display existing receipt of given expense ids.
+         This function returns an action that display existing account.move of given expense ids.
          '''
          assert len(ids) == 1, 'This option should only be used for a single id at a time'
-         voucher_id = self.browse(cr, uid, ids[0], context=context).voucher_id.id
-         res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account_voucher', 'view_purchase_receipt_form')
+         expense = self.browse(cr, uid, ids[0], context=context)
+         assert expense.account_move_id
+         try:
+             dummy, view_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'view_move_form')
+         except ValueError, e:
+             view_id = False
          result = {
-             'name': _('Expense Receipt'),
+             'name': _('Expense Account Move'),
              'view_type': 'form',
              'view_mode': 'form',
-             'view_id': res and res[1] or False,
-             'res_model': 'account.voucher',
+             'view_id': view_id,
+             'res_model': 'account.move',
              'type': 'ir.actions.act_window',
              'nodestroy': True,
              'target': 'current',
@@@ -67,7 -67,7 +67,7 @@@
                      <button name="refuse" states="confirm,accepted" string="Refuse" type="workflow" groups="base.group_hr_user" />
                      <button name="draft" states="confirm,cancelled" string="Set to Draft" type="workflow" groups="base.group_hr_user" />
                      <button name="done" states="accepted" string="Generate Accounting Entries" type="workflow" groups="account.group_account_invoice" class="oe_highlight"/>
-                     <button name="action_view_receipt" states="done" string="Open Receipt" type="object"/>
 -                    <button name="action_view_receipt" states="done" string="Open Accounting Entries" type="object" groups="account.group_account_invoice"/>
++                    <button name="action_view_move" states="done" string="Open Accounting Entries" type="object" groups="account.group_account_invoice"/>
                      <field name="state" widget="statusbar" statusbar_visible="draft,confirm,accepted,done" statusbar_colors='{"confirm":"blue","cancelled":"red"}'/>
                  </header>
                  <sheet>
                              <group>
                                  <group string="Accounting Data">
                                      <field name="journal_id" widget="selection" domain="[('type', '=', 'purchase')]"/>
-                                     <field name="voucher_id" context="{'form_view_ref': 'account_voucher.view_purchase_receipt_form'}"/>
+                                     <field name="account_move_id"/>
 -                                    <field name="voucher_id" invisible="1" context="{'form_view_ref': 'account_voucher.view_purchase_receipt_form'}"/>
                                  </group>
                              </group>
                          </page>
@@@ -43,7 -43,7 +43,7 @@@
              <field name="wkf_id" ref="wkf_expenses"/>
              <field name="name">done</field>
              <field name="kind">function</field>
--            <field name="action">action_receipt_create()</field>
++            <field name="action">action_move_create()</field>
          </record>
  
          <record id="t1" model="workflow.transition">
Simple merge
Simple merge
@@@ -518,9 -547,14 +547,14 @@@ openerp.mail = function (session) 
                      'default_composition_mode': default_composition_mode,
                      'default_parent_id': self.id,
                      'default_body': mail.ChatterUtils.get_text2html(self.$el ? (self.$el.find('textarea:not(.oe_compact)').val() || '') : ''),
 -                    'default_attachment_ids': self.attachment_ids,
 +                    'default_attachment_ids': _.map(self.attachment_ids, function (file) {return file.id;}),
                      'default_partner_ids': partner_ids,
+                     'mail_post_autofollow': true,
+                     'mail_post_autofollow_partner_ids': partner_ids,
                  };
+                 if (self.is_log) {
+                     _.extend(context, {'mail_compose_log': true});
+                 }
                  if (default_composition_mode != 'reply' && self.context.default_model && self.context.default_res_id) {
                      context.default_model = self.context.default_model;
                      context.default_res_id = self.context.default_res_id;
Simple merge
Simple merge
@@@ -704,8 -706,9 +704,9 @@@ class pos_order(osv.osv)
          @return: True
          """
          stock_picking_obj = self.pool.get('stock.picking')
+         wf_service = netsvc.LocalService("workflow")
          for order in self.browse(cr, uid, ids, context=context):
 -            wf_service.trg_validate(uid, 'stock.picking', order.picking_id.id, 'button_cancel', cr)
 +            stock_picking_obj.signal_button_cancel(cr, uid, [order.picking_id.id])
              if stock_picking_obj.browse(cr, uid, order.picking_id.id, context=context).state <> 'cancel':
                  raise osv.except_osv(_('Error!'), _('Unable to cancel the picking.'))
          self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
@@@ -67,11 -67,13 +67,12 @@@ class StockMove(osv.osv)
          'procurements': fields.one2many('procurement.order', 'move_id', 'Procurements'),
      }
  
-     def copy(self, cr, uid, id, default=None, context=None):
-         default = default or {}
+     def copy_data(self, cr, uid, id, default=None, context=None):
+         if default is None:
+             default = {}
          default['procurements'] = []
-         return super(StockMove, self).copy(cr, uid, id, default, context=context)
+         return super(StockMove, self).copy_data(cr, uid, id, default, context=context)
  
 -StockMove()
  
  class procurement_order(osv.osv):
      """
Simple merge
Simple merge
Simple merge
                  </header>
                  <sheet string="Issue">
                      <label for="name" class="oe_edit_only"/>
 -                    <h1><field name="name"/></h1>
 +                    <h1>
 +                        <field name="name" class="oe_inline"/>
 +                        <field name="kanban_state" invisible='1'/>
 +                        <button name="set_kanban_state_done" help="In Progress" attrs="{'invisible': [('kanban_state', 'in', ['done','blocked'])]}" type="object" icon="gtk-normal" class="oe_link oe_right"/>
 +                        <button name="set_kanban_state_blocked" help="Ready for Next Stage" attrs="{'invisible': [('kanban_state', 'in', ['normal','blocked'])]}" type="object" icon="gtk-yes" class="oe_link oe_right"/>
 +                        <button name="set_kanban_state_normal" help="Blocked" attrs="{'invisible': [('kanban_state', 'in', ['done','normal'])]}" type="object" icon="gtk-no" class="oe_link oe_right"/>
 +                    </h1>
+                     <label for="categ_ids" class="oe_edit_only"/>
+                     <field name="categ_ids" widget="many2many_tags"/>
                      <group>
                          <group groups="base.group_user">
                              <field name="user_id"/>
Simple merge
Simple merge
Simple merge
Simple merge