[IMP] Contract management + fixes
bzr revid: fp@tinyerp.com-20111116145958-pzp36c49wh6028p9
This commit is contained in:
parent
1d54432f87
commit
36acb24ce4
|
@ -91,15 +91,18 @@
|
|||
</group>
|
||||
<notebook colspan="4">
|
||||
<page string="Account Data">
|
||||
<field name="partner_id"/>
|
||||
<field name="contact_id"/>
|
||||
<field name="currency_id"/>
|
||||
<newline/>
|
||||
<field name="date_start"/>
|
||||
<field name="date"/>
|
||||
<newline/>
|
||||
<field name="quantity_max"/>
|
||||
<field name="user_id"/>
|
||||
<group colspan="2" col="2">
|
||||
<separator colspan="2" string="Contacts"/>
|
||||
<field name="partner_id" on_change="on_change_partner_id(partner_id)"/>
|
||||
<field name="contact_id"/>
|
||||
<field name="user_id"/>
|
||||
</group>
|
||||
<group colspan="2" col="2" name="contract">
|
||||
<separator colspan="2" string="Contract Data"/>
|
||||
<field name="date_start"/>
|
||||
<field name="date"/>
|
||||
<field name="quantity_max"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Description">
|
||||
<field colspan="4" name="description" nolabel="1"/>
|
||||
|
|
|
@ -376,13 +376,11 @@ class account_analytic_account(osv.osv):
|
|||
|
||||
def _is_overdue_quantity(self, cr, uid, ids, fieldnames, args, context=None):
|
||||
result = dict.fromkeys(ids, 0)
|
||||
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
if record.quantity == 0.0 and record.quantity_max == 0.0:
|
||||
result[record.id] = 0
|
||||
else:
|
||||
if record.quantity_max > 0.0:
|
||||
result[record.id] = int(record.quantity >= record.quantity_max)
|
||||
|
||||
else:
|
||||
result[record.id] = 0
|
||||
return result
|
||||
|
||||
def _get_analytic_account(self, cr, uid, ids, context=None):
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<openerp>
|
||||
<data>
|
||||
<menuitem icon="terp-project" id="base.menu_main_pm" name="Project" sequence="10"/>
|
||||
<menuitem id="menu_invoicing" name="Billing" parent="base.menu_main_pm" sequence="4" groups="base.group_extended"/>
|
||||
<menuitem id="base.menu_invoiced" name="Invoicing" parent="base.menu_base_partner" sequence="5"/>
|
||||
|
||||
<record id="action_hr_tree_invoiced_all" model="ir.actions.act_window">
|
||||
<field name="name">All Uninvoiced Entries</field>
|
||||
|
@ -11,16 +10,7 @@
|
|||
<field name="domain">[('invoice_id','=',False)]</field>
|
||||
<field name="search_view_id" ref="account.view_account_analytic_line_filter"/>
|
||||
</record>
|
||||
<menuitem action="action_hr_tree_invoiced_all" id="menu_action_hr_tree_invoiced_all" parent="menu_invoicing" groups="analytic.group_analytic_accounting"/>
|
||||
|
||||
<record id="action_account_analytic_managed_overpassed" model="ir.actions.act_window">
|
||||
<field name="name">Overpassed Accounts</field>
|
||||
<field name="res_model">account.analytic.account</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,graph</field>
|
||||
<field name="domain">[('date','<=',time.strftime('%Y-%m-%d')),('state','=','open')]</field>
|
||||
</record>
|
||||
<menuitem action="action_account_analytic_managed_overpassed" id="menu_action_account_analytic_managed_overpassed" sequence="50" parent="menu_invoicing" groups="base.group_extended"/>
|
||||
<menuitem action="action_hr_tree_invoiced_all" id="menu_action_hr_tree_invoiced_all" parent="base.menu_invoiced"/>
|
||||
|
||||
<record id="view_account_analytic_account_overdue_search" model="ir.ui.view">
|
||||
<field name="name">account.analytic.account.search</field>
|
||||
|
@ -29,11 +19,10 @@
|
|||
<field name="arch" type="xml">
|
||||
<search string="Analytic Account">
|
||||
<group col="8" colspan="4">
|
||||
<filter icon="terp-check" name="draft" string="Draft" domain="[('state','=','draft')]" help="Contracts not signed yet"/>
|
||||
<filter icon="terp-camera_test" name="open" string="Open" domain="[('state','=','open')]" help="Contracts in progress"/>
|
||||
<filter icon="terp-camera_test" name="open" string="Open" domain="[('state','in',('open','draft'))]" help="Contracts in progress"/>
|
||||
<filter icon="terp-gtk-media-pause" name="pending" string="Pending" domain="[('state','=','pending')]" help="Pending contracts to renew with your customer"/>
|
||||
<separator orientation="vertical"/>
|
||||
<filter icon="terp-go-today" string="To Renew" domain="['|', '&', ('date', '!=', False), ('date', '<', time.strftime('%%Y-%%m-%%d')), ('is_overdue_quantity', '=', True)]"
|
||||
<filter icon="terp-go-today" string="To Renew" domain="['|', '&', ('date', '!=', False), ('date', '<=', time.strftime('%%Y-%%m-%%d')), ('is_overdue_quantity', '=', True)]"
|
||||
name="renew"
|
||||
help="The contracts to be renewed because the deadline is passed or the working hours are higher than the allocated hours" />
|
||||
<filter icon="terp-go-month"
|
||||
|
@ -71,7 +60,7 @@
|
|||
|
||||
|
||||
<record id="action_account_analytic_overdue" model="ir.actions.act_window">
|
||||
<field name="name">Overdue Accounts</field>
|
||||
<field name="name">Analytic Accounts/Contracts</field>
|
||||
<field name="res_model">account.analytic.account</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,graph</field>
|
||||
|
@ -80,7 +69,7 @@
|
|||
<field name="search_view_id" ref="view_account_analytic_account_overdue_search"/>
|
||||
<field name="help">You will find here the contracts to be renewed because the deadline is passed or the working hours are higher than the allocated hours. OpenERP automatically sets these analytic accounts to the pending state, in order to raise a warning during the timesheets recording. Salesmen should review all pending accounts and reopen or close the according to the negotiation with the customer.</field>
|
||||
</record>
|
||||
<menuitem action="action_account_analytic_overdue" id="menu_action_account_analytic_overdue" sequence="50" parent="sale.menu_invoiced" groups="base.group_extended"/>
|
||||
<menuitem action="action_account_analytic_overdue" id="menu_action_account_analytic_overdue" sequence="50" parent="base.menu_invoiced"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -8,6 +8,27 @@
|
|||
Remove information on Account data => because they move on analysis sheet
|
||||
create a page with invoicing informations
|
||||
-->
|
||||
<record id="account_analytic_account_invoice_stat_form" model="ir.ui.view">
|
||||
<field name="name">account.analytic.account.invoice.stat.form.inherit</field>
|
||||
<field name="model">account.analytic.account</field>
|
||||
<field name="type">form</field>
|
||||
<field name="inherit_id" ref="hr_timesheet_invoice.account_analytic_account_form_form"/>
|
||||
<field eval="18" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<group name="invoice_stats" position="inside">
|
||||
<field name="hours_qtt_non_invoiced"/>
|
||||
<field name="ca_to_invoice"/>
|
||||
<label string="" colspan="1"/>
|
||||
<button
|
||||
name="%(hr_timesheet_invoice.action_hr_timesheet_invoice_create_final)d"
|
||||
string="Create Invoice"
|
||||
type="action"
|
||||
attrs="{'readonly':[('ca_to_invoice','=',0.0)]}"
|
||||
icon="gtk-go-forward"/>
|
||||
</group>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_timesheet.account_analytic_account_form_form" model="ir.ui.view">
|
||||
<field name="name">account.analytic.account.invoice.form.inherit</field>
|
||||
<field name="model">account.analytic.account</field>
|
||||
|
@ -16,7 +37,7 @@
|
|||
<field eval="18" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<notebook position="inside">
|
||||
<page string="Analysis summary">
|
||||
<page string="Invoice Analysis">
|
||||
<separator colspan="4" string="Work done stats"/>
|
||||
|
||||
<field name="total_cost"/>
|
||||
|
@ -39,10 +60,6 @@
|
|||
<field name="last_invoice_date"/>
|
||||
<field name="last_worked_invoiced_date"/>
|
||||
<field name="last_worked_date"/>
|
||||
<separator colspan="4" string="To be invoiced"/>
|
||||
|
||||
<field name="hours_qtt_non_invoiced"/>
|
||||
<field name="ca_to_invoice"/>
|
||||
</page>
|
||||
<page string="Stats by month">
|
||||
<field colspan="4" name="month_ids" nolabel="1">
|
||||
|
|
|
@ -150,7 +150,7 @@ class account_analytic_account(osv.osv):
|
|||
_columns = {
|
||||
'name': fields.char('Account Name', size=128, required=True),
|
||||
'complete_name': fields.function(_complete_name_calc, type='char', string='Full Account Name'),
|
||||
'code': fields.char('Account Code', size=24, select=True),
|
||||
'code': fields.char('Code/Reference', size=24, select=True),
|
||||
'type': fields.selection([('view','View'), ('normal','Normal')], 'Account Type', help='If you select the View Type, it means you won\'t allow to create journal entries using that account.'),
|
||||
'description': fields.text('Description'),
|
||||
'parent_id': fields.many2one('account.analytic.account', 'Parent Analytic Account', select=2),
|
||||
|
@ -168,7 +168,7 @@ class account_analytic_account(osv.osv):
|
|||
'date_start': fields.date('Date Start'),
|
||||
'date': fields.date('Date End', select=True),
|
||||
'company_id': fields.many2one('res.company', 'Company', required=False), #not required because we want to allow different companies to use the same chart of account, except for leaf accounts.
|
||||
'state': fields.selection([('draft','New'),('open','Started'), ('pending','Pending'),('cancelled', 'Cancelled'),('close','Closed'),('template', 'Template')], 'State', required=True,
|
||||
'state': fields.selection([('template', 'Template'),('draft','New'),('open','Open'), ('pending','Pending'),('cancelled', 'Cancelled'),('close','Closed')], 'State', required=True,
|
||||
help='* When an account is created its in \'Draft\' state.\
|
||||
\n* If any associated partner is there, it can be in \'Open\' state.\
|
||||
\n* If any pending balance is there it can be in \'Pending\'. \
|
||||
|
@ -217,6 +217,12 @@ class account_analytic_account(osv.osv):
|
|||
default['line_ids'] = []
|
||||
return super(account_analytic_account, self).copy(cr, uid, id, default, context=context)
|
||||
|
||||
def on_change_partner_id(self, cr, uid, id, partner_id, context={}):
|
||||
if not partner_id:
|
||||
return {'value': {'contact_id': False}}
|
||||
addr = self.pool.get('res.partner').address_get(cr, uid, [partner_id], ['invoice'])
|
||||
return {'value': {'contact_id': addr.get('invoice', False)}}
|
||||
|
||||
def on_change_company(self, cr, uid, id, company_id):
|
||||
if not company_id:
|
||||
return {}
|
||||
|
|
|
@ -63,12 +63,12 @@ class account_analytic_account(osv.osv):
|
|||
|
||||
_inherit = "account.analytic.account"
|
||||
_columns = {
|
||||
'pricelist_id': fields.many2one('product.pricelist', 'Sale Pricelist',
|
||||
'pricelist_id': fields.many2one('product.pricelist', 'Customer Pricelist',
|
||||
help="The product to invoice is defined on the employee form, the price will be deduced by this pricelist on the product."),
|
||||
'amount_max': fields.float('Max. Invoice Price'),
|
||||
'amount_invoiced': fields.function(_invoiced_calc, string='Invoiced Amount',
|
||||
help="Total invoiced"),
|
||||
'to_invoice': fields.many2one('hr_timesheet_invoice.factor', 'Reinvoice Costs',
|
||||
'to_invoice': fields.many2one('hr_timesheet_invoice.factor', 'Invoice on Timesheet & Costs',
|
||||
help="Fill this field if you plan to automatically generate invoices based " \
|
||||
"on the costs in this analytic account: timesheets, expenses, ..." \
|
||||
"You can configure an automatic invoice rate on analytic accounts."),
|
||||
|
@ -76,6 +76,15 @@ class account_analytic_account(osv.osv):
|
|||
_defaults = {
|
||||
'pricelist_id': lambda self, cr, uid, ctx: ctx.get('pricelist_id', False),
|
||||
}
|
||||
def on_change_partner_id(self, cr, uid, id, partner_id, context={}):
|
||||
res = super(account_analytic_account, self).on_change_partner_id(cr, uid, id, partner_id, context)
|
||||
if (not res.get('value', False)) or not partner_id:
|
||||
return res
|
||||
part = self.pool.get('res.partner').browse(cr, uid, partner_id)
|
||||
pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False
|
||||
if pricelist:
|
||||
res['value']['pricelist_id'] = pricelist
|
||||
return res
|
||||
|
||||
def set_close(self, cr, uid, ids, context=None):
|
||||
return self.write(cr, uid, ids, {'state':'close'}, context=context)
|
||||
|
|
|
@ -7,23 +7,27 @@
|
|||
<field name="type">form</field>
|
||||
<field name="inherit_id" ref="account.view_account_analytic_account_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="user_id" position="after">
|
||||
<group colspan="4" col="4">
|
||||
<separator colspan="4" string="Invoicing Data"/>
|
||||
<field name="pricelist_id" domain="[('type','=','sale')]" widget="selection"/>
|
||||
<field name="to_invoice" widget="selection" />
|
||||
<group name="contract" position="after">
|
||||
<group colspan="2" col="2" name="invoice_data">
|
||||
<separator colspan="2" string="Invoicing Data"/>
|
||||
<field name="to_invoice" widget="selection"/>
|
||||
<field name="pricelist_id" domain="[('type','=','sale')]" attrs="{'required':[('to_invoice','!=',False)]}"/>
|
||||
<field name="amount_max"/>
|
||||
<field name="amount_invoiced"/>
|
||||
<separator colspan="4"/>
|
||||
<group col="9" colspan="8">
|
||||
<field name="state" readonly="1"/>
|
||||
<button name="set_cancel" string="Cancel" type="object" states="open,pending" icon="gtk-cancel"/>
|
||||
<button name="set_open" string="Reactivate Account" type="object" states="pending,cancelled,close,draft" icon="gtk-ok"/>
|
||||
<button name="set_pending" string="Pending" type="object" states="open" icon="gtk-media-pause"/>
|
||||
<button name="set_close" string="Close" type="object" states="open,pending" icon="terp-dialog-close"/>
|
||||
</group>
|
||||
</group>
|
||||
</field>
|
||||
<group colspan="2" col="2" name="invoice_stats">
|
||||
<separator colspan="4" string="Invoicing Statistics"/>
|
||||
<field name="amount_invoiced"/>
|
||||
</group>
|
||||
<separator colspan="4"/>
|
||||
<group col="9" colspan="8">
|
||||
<field name="state" readonly="1" widget="statusbar"
|
||||
statusbar_visible="open,pending,close" statusbar_colors='{"pending":"red", "template":"blue"}'/>
|
||||
<button name="set_cancel" string="Cancel" type="object" states="open,pending" icon="gtk-cancel"/>
|
||||
<button name="set_open" string="Reactivate Account" type="object" states="pending,cancelled,close,draft" icon="gtk-ok"/>
|
||||
<button name="set_pending" string="Pending" type="object" states="open" icon="gtk-media-pause"/>
|
||||
<button name="set_close" string="Close" type="object" states="open,pending" icon="terp-dialog-close"/>
|
||||
</group>
|
||||
</group>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
@ -123,7 +127,7 @@
|
|||
<field name="view_mode">tree,form</field>
|
||||
<field name="domain">[]</field>
|
||||
<field name="context">{'search_default_to_invoice': 1}</field>
|
||||
<field name="search_view_id" ref="account.view_account_analytic_line_filter"/>
|
||||
<field name="search_view_id" ref="account.view_account_analytic_line_filter"/>
|
||||
<field name="help">This list shows you every task you can invoice to the customer. Select the lines and click the Action button to generate the invoices automatically.</field>
|
||||
</record>
|
||||
<menuitem
|
||||
|
|
|
@ -39,145 +39,21 @@ class final_invoice_create(osv.osv_memory):
|
|||
'time': fields.boolean('Time spent', help='Display time in the history of works'),
|
||||
'name': fields.boolean('Name of entry', help='Display detail of work in the invoice line.'),
|
||||
'price': fields.boolean('Cost', help='Display cost of the item you reinvoice'),
|
||||
'balance_product': fields.many2one('product.product', 'Balance product', help='The product that will be used to invoice the remaining amount'),
|
||||
}
|
||||
'product': fields.many2one('product.product', 'Product', help='The product that will be used to invoice the remaining amount'),
|
||||
}
|
||||
|
||||
def do_create(self, cr, uid, ids, context=None):
|
||||
data = self.read(cr, uid, ids, [], context=context)[0]
|
||||
ids = self.pool.get('account.analytic.line').search(cr, uid, [('invoice_id','=',False),('to_invoice','<>', False), ('account_id', 'in', context['active_ids'])], context=context)
|
||||
invs = self.pool.get('account.analytic.line').invoice_cost_create(cr, uid, ids, data, context=context)
|
||||
mod_obj = self.pool.get('ir.model.data')
|
||||
analytic_account_obj = self.pool.get('account.analytic.account')
|
||||
res_partner_obj = self.pool.get('res.partner')
|
||||
account_payment_term_obj = self.pool.get('account.payment.term')
|
||||
invoice_obj = self.pool.get('account.invoice')
|
||||
product_obj = self.pool.get('product.product')
|
||||
fiscal_pos_obj = self.pool.get('account.fiscal.position')
|
||||
invoice_line_obj = self.pool.get('account.invoice.line')
|
||||
invoices = []
|
||||
|
||||
if context is None:
|
||||
context = {}
|
||||
result = mod_obj._get_id(cr, uid, 'account', 'view_account_invoice_filter')
|
||||
res = mod_obj.read(cr, uid, result, ['res_id'], context=context)
|
||||
|
||||
data = self.browse(cr, uid, ids, context=context)[0]
|
||||
balance_product = data.balance_product.id
|
||||
|
||||
account_ids = 'active_ids' in context and context['active_ids'] or []
|
||||
|
||||
for account in analytic_account_obj.browse(cr, uid, account_ids, context=context):
|
||||
partner = account.partner_id
|
||||
amount_total=0.0
|
||||
if (not partner) or not (account.pricelist_id):
|
||||
raise osv.except_osv(_('Analytic account incomplete'),
|
||||
_('Please fill in the partner and pricelist field '
|
||||
'in the analytic account:\n%s') % (account.name,))
|
||||
|
||||
date_due = False
|
||||
if partner.property_payment_term:
|
||||
pterm_list= account_payment_term_obj.compute(cr, uid,
|
||||
partner.property_payment_term.id, value=1,
|
||||
date_ref=time.strftime('%Y-%m-%d'))
|
||||
if pterm_list:
|
||||
pterm_list = [line[0] for line in pterm_list]
|
||||
pterm_list.sort()
|
||||
date_due = pterm_list[-1]
|
||||
|
||||
curr_invoice = {
|
||||
'name': time.strftime('%d/%m/%Y')+' - '+account.name,
|
||||
'partner_id': account.partner_id.id,
|
||||
'address_contact_id': res_partner_obj.address_get(cr, uid, [account.partner_id.id], adr_pref=['contact'])['contact'],
|
||||
'address_invoice_id': res_partner_obj.address_get(cr, uid, [account.partner_id.id], adr_pref=['invoice'])['invoice'],
|
||||
'payment_term': partner.property_payment_term.id or False,
|
||||
'account_id': partner.property_account_receivable.id,
|
||||
'currency_id': account.pricelist_id.currency_id.id,
|
||||
'date_due': date_due,
|
||||
'fiscal_position': account.partner_id.property_account_position.id
|
||||
}
|
||||
last_invoice = invoice_obj.create(cr, uid, curr_invoice, context=context)
|
||||
invoices.append(last_invoice)
|
||||
|
||||
context2 = context.copy()
|
||||
context2['lang'] = partner.lang
|
||||
|
||||
cr.execute("""SELECT
|
||||
line.product_id,
|
||||
sum(line.amount),
|
||||
line.general_account_id,
|
||||
line.product_uom_id,
|
||||
move_line.ref
|
||||
FROM
|
||||
account_analytic_line as line
|
||||
LEFT JOIN account_move_line as move_line on (line.move_id=move_line.id)
|
||||
LEFT JOIN account_analytic_journal as journal on (line.journal_id=journal.id)
|
||||
WHERE
|
||||
line.account_id = %s AND
|
||||
line.move_id IS NOT NULL AND
|
||||
journal.type = 'sale'
|
||||
GROUP BY
|
||||
line.product_id,
|
||||
line.general_account_id,
|
||||
line.product_uom_id,
|
||||
move_line.ref""", (account.id,))
|
||||
for product_id, amount, account_id, product_uom_id, ref in cr.fetchall():
|
||||
product = product_obj.browse(cr, uid, product_id, context2)
|
||||
|
||||
if product:
|
||||
taxes = product.taxes_id
|
||||
else:
|
||||
taxes = []
|
||||
|
||||
tax = fiscal_pos_obj.map_tax(cr, uid, account.partner_id.property_account_position, taxes)
|
||||
if not account_id:
|
||||
raise osv.except_osv(_("Configuration Error"), _("No income account defined for product '%s'") % product.name)
|
||||
curr_line = {
|
||||
'price_unit': -amount,
|
||||
'quantity': 1.0,
|
||||
'discount': 0.0,
|
||||
'invoice_line_tax_id': [(6,0,tax)],
|
||||
'invoice_id': last_invoice,
|
||||
'name': ref or '' +(product and ' - '+product.name or ''),
|
||||
'product_id': product_id,
|
||||
'uos_id': product_uom_id,
|
||||
'account_id': account_id,
|
||||
'account_analytic_id': account.id
|
||||
}
|
||||
invoice_line_obj.create(cr, uid, curr_line, context=context)
|
||||
|
||||
if not balance_product:
|
||||
raise osv.except_osv(_('Balance product needed'), _('Please fill a Balance product in the wizard'))
|
||||
product = product_obj.browse(cr, uid, balance_product, context=context2)
|
||||
taxes = product.taxes_id
|
||||
tax = fiscal_pos_obj.map_tax(cr, uid, account.partner_id.property_account_position, taxes)
|
||||
account_id = product.product_tmpl_id.property_account_income.id or product.categ_id.property_account_income_categ.id
|
||||
if not account_id:
|
||||
raise osv.except_osv(_("Configuration Error"), _("No income account defined for product '%s'") % product.name)
|
||||
curr_line = {
|
||||
'price_unit': account.amount_max - amount_total,
|
||||
'quantity': 1.0,
|
||||
'discount': 0.0,
|
||||
'invoice_line_tax_id': [(6,0,tax)],
|
||||
'invoice_id': last_invoice,
|
||||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'uos_id': product.uom_id.id,
|
||||
'account_id': account_id,
|
||||
'account_analytic_id': account.id
|
||||
}
|
||||
invoice_line_obj.create(cr, uid, curr_line, context=context)
|
||||
if account.amount_max < amount_total:
|
||||
invoice_obj.write(cr, uid, [last_invoice], {'type': 'out_refund',}, context=context)
|
||||
cr.execute('update account_analytic_line set invoice_id=%s where invoice_id is null and account_id=%s', (last_invoice, account.id))
|
||||
|
||||
return {
|
||||
'domain': "[('id','in', ["+','.join(map(str,invoices))+"])]",
|
||||
'name': 'Invoices',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'account.invoice',
|
||||
'view_id': False,
|
||||
'context': "{'type':'out_invoice'}",
|
||||
'type': 'ir.actions.act_window',
|
||||
'search_view_id': res['res_id']
|
||||
}
|
||||
act_obj = self.pool.get('ir.actions.act_window')
|
||||
mod_ids = mod_obj.search(cr, uid, [('name', '=', 'action_invoice_tree1')], context=context)[0]
|
||||
res_id = mod_obj.read(cr, uid, mod_ids, ['res_id'], context=context)['res_id']
|
||||
act_win = act_obj.read(cr, uid, res_id, [], context=context)
|
||||
act_win['domain'] = [('id','in',invs),('type','=','out_invoice')]
|
||||
act_win['name'] = _('Invoices')
|
||||
return act_win
|
||||
|
||||
final_invoice_create()
|
||||
|
||||
|
|
|
@ -27,43 +27,18 @@ from tools.translate import _
|
|||
## Create an invoice based on selected timesheet lines
|
||||
#
|
||||
|
||||
#
|
||||
# TODO: check unit of measure !!!
|
||||
#
|
||||
class hr_timesheet_invoice_create(osv.osv_memory):
|
||||
class account_analytic_line(osv.osv):
|
||||
_inherit = "account.analytic.line"
|
||||
|
||||
_name = 'hr.timesheet.invoice.create'
|
||||
_description = 'Create invoice from timesheet'
|
||||
_columns = {
|
||||
'date': fields.boolean('Date', help='The real date of each work will be displayed on the invoice'),
|
||||
'time': fields.boolean('Time spent', help='The time of each work done will be displayed on the invoice'),
|
||||
'name': fields.boolean('Description', help='The detail of each work done will be displayed on the invoice'),
|
||||
'price': fields.boolean('Cost', help='The cost of each work done will be displayed on the invoice. You probably don\'t want to check this'),
|
||||
'product': fields.many2one('product.product', 'Product', help='Complete this field only if you want to force to use a specific product. Keep empty to use the real product that comes from the cost.'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'date': lambda *args: 1,
|
||||
'name': lambda *args: 1
|
||||
}
|
||||
|
||||
def view_init(self, cr, uid, fields, context=None):
|
||||
"""
|
||||
This function checks for precondition before wizard executes
|
||||
@param self: The object pointer
|
||||
@param cr: the current row, from the database cursor,
|
||||
@param uid: the current user’s ID for security checks,
|
||||
@param fields: List of fields for default value
|
||||
@param context: A standard dictionary for contextual values
|
||||
"""
|
||||
analytic_obj = self.pool.get('account.analytic.line')
|
||||
data = context and context.get('active_ids', [])
|
||||
for analytic in analytic_obj.browse(cr, uid, data, context=context):
|
||||
if analytic.invoice_id:
|
||||
raise osv.except_osv(_('Warning !'), _("Invoice is already linked to some of the analytic line(s)!"))
|
||||
|
||||
def do_create(self, cr, uid, ids, context=None):
|
||||
mod_obj = self.pool.get('ir.model.data')
|
||||
#
|
||||
# data = {
|
||||
# 'date': boolean
|
||||
# 'time': boolean
|
||||
# 'name': boolean
|
||||
# 'price': boolean
|
||||
# 'product': many2one id
|
||||
# }
|
||||
def invoice_cost_create(self, cr, uid, ids, data={}, context=None):
|
||||
analytic_account_obj = self.pool.get('account.analytic.account')
|
||||
res_partner_obj = self.pool.get('res.partner')
|
||||
account_payment_term_obj = self.pool.get('account.payment.term')
|
||||
|
@ -77,11 +52,9 @@ class hr_timesheet_invoice_create(osv.osv_memory):
|
|||
invoices = []
|
||||
if context is None:
|
||||
context = {}
|
||||
result = mod_obj._get_id(cr, uid, 'account', 'view_account_invoice_filter')
|
||||
data = self.read(cr, uid, ids, [], context=context)[0]
|
||||
|
||||
account_ids = {}
|
||||
for line in self.pool.get('account.analytic.line').browse(cr, uid, context['active_ids'], context=context):
|
||||
for line in self.pool.get('account.analytic.line').browse(cr, uid, ids, context=context):
|
||||
account_ids[line.account_id.id] = True
|
||||
|
||||
account_ids = account_ids.keys() #data['accounts']
|
||||
|
@ -127,7 +100,7 @@ class hr_timesheet_invoice_create(osv.osv_memory):
|
|||
"FROM account_analytic_line as line " \
|
||||
"WHERE account_id = %s " \
|
||||
"AND id IN %s AND to_invoice IS NOT NULL " \
|
||||
"GROUP BY product_id,to_invoice,product_uom_id", (account.id, tuple(context['active_ids']),))
|
||||
"GROUP BY product_id,to_invoice,product_uom_id", (account.id, tuple(ids),))
|
||||
|
||||
for product_id, factor_id, qty, uom in cr.fetchall():
|
||||
product = product_obj.browse(cr, uid, product_id, context2)
|
||||
|
@ -135,7 +108,7 @@ class hr_timesheet_invoice_create(osv.osv_memory):
|
|||
raise osv.except_osv(_('Error'), _('At least one line has no product !'))
|
||||
factor_name = ''
|
||||
factor = invoice_factor_obj.browse(cr, uid, factor_id, context2)
|
||||
if not data['product']:
|
||||
if not data.get('product', False):
|
||||
if factor.customer_name:
|
||||
factor_name = product.name+' - '+factor.customer_name
|
||||
else:
|
||||
|
@ -148,7 +121,7 @@ class hr_timesheet_invoice_create(osv.osv_memory):
|
|||
ctx.update({'uom':uom})
|
||||
if account.pricelist_id:
|
||||
pl = account.pricelist_id.id
|
||||
price = pro_price_obj.price_get(cr,uid,[pl], data['product'] or product_id, qty or 1.0, account.partner_id.id, context=ctx)[pl]
|
||||
price = pro_price_obj.price_get(cr,uid,[pl], data.get('product', False) or product_id, qty or 1.0, account.partner_id.id, context=ctx)[pl]
|
||||
else:
|
||||
price = 0.0
|
||||
|
||||
|
@ -164,7 +137,7 @@ class hr_timesheet_invoice_create(osv.osv_memory):
|
|||
'invoice_line_tax_id': [(6,0,tax )],
|
||||
'invoice_id': last_invoice,
|
||||
'name': factor_name,
|
||||
'product_id': data['product'] or product_id,
|
||||
'product_id': data.get('product',product_id),
|
||||
'invoice_line_tax_id': [(6,0,tax)],
|
||||
'uos_id': uom,
|
||||
'account_id': account_id,
|
||||
|
@ -174,37 +147,76 @@ class hr_timesheet_invoice_create(osv.osv_memory):
|
|||
#
|
||||
# Compute for lines
|
||||
#
|
||||
cr.execute("SELECT * FROM account_analytic_line WHERE account_id = %s and id IN %s AND product_id=%s and to_invoice=%s ORDER BY account_analytic_line.date", (account.id, tuple(context['active_ids']), product_id, factor_id))
|
||||
cr.execute("SELECT * FROM account_analytic_line WHERE account_id = %s and id IN %s AND product_id=%s and to_invoice=%s ORDER BY account_analytic_line.date", (account.id, tuple(ids), product_id, factor_id))
|
||||
|
||||
line_ids = cr.dictfetchall()
|
||||
note = []
|
||||
for line in line_ids:
|
||||
# set invoice_line_note
|
||||
details = []
|
||||
if data['date']:
|
||||
if data.get('date', False):
|
||||
details.append(line['date'])
|
||||
if data['time']:
|
||||
if data.get('time', False):
|
||||
if line['product_uom_id']:
|
||||
details.append("%s %s" % (line['unit_amount'], product_uom_obj.browse(cr, uid, [line['product_uom_id']],context2)[0].name))
|
||||
else:
|
||||
details.append("%s" % (line['unit_amount'], ))
|
||||
if data['name']:
|
||||
if data.get('name', False):
|
||||
details.append(line['name'])
|
||||
note.append(u' - '.join(map(lambda x: unicode(x) or '',details)))
|
||||
|
||||
curr_line['note'] = "\n".join(map(lambda x: unicode(x) or '',note))
|
||||
invoice_line_obj.create(cr, uid, curr_line, context=context)
|
||||
cr.execute("update account_analytic_line set invoice_id=%s WHERE account_id = %s and id IN %s", (last_invoice, account.id, tuple(context['active_ids'])))
|
||||
cr.execute("update account_analytic_line set invoice_id=%s WHERE account_id = %s and id IN %s", (last_invoice, account.id, tuple(ids)))
|
||||
|
||||
invoice_obj.button_reset_taxes(cr, uid, [last_invoice], context)
|
||||
return invoices
|
||||
|
||||
#
|
||||
# TODO: check unit of measure !!!
|
||||
#
|
||||
|
||||
class hr_timesheet_invoice_create(osv.osv_memory):
|
||||
|
||||
_name = 'hr.timesheet.invoice.create'
|
||||
_description = 'Create invoice from timesheet'
|
||||
_columns = {
|
||||
'date': fields.boolean('Date', help='The real date of each work will be displayed on the invoice'),
|
||||
'time': fields.boolean('Time spent', help='The time of each work done will be displayed on the invoice'),
|
||||
'name': fields.boolean('Description', help='The detail of each work done will be displayed on the invoice'),
|
||||
'price': fields.boolean('Cost', help='The cost of each work done will be displayed on the invoice. You probably don\'t want to check this'),
|
||||
'product': fields.many2one('product.product', 'Product', help='Complete this field only if you want to force to use a specific product. Keep empty to use the real product that comes from the cost.'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'date': lambda *args: 1,
|
||||
'name': lambda *args: 1
|
||||
}
|
||||
|
||||
def view_init(self, cr, uid, fields, context=None):
|
||||
"""
|
||||
This function checks for precondition before wizard executes
|
||||
@param self: The object pointer
|
||||
@param cr: the current row, from the database cursor,
|
||||
@param uid: the current user’s ID for security checks,
|
||||
@param fields: List of fields for default value
|
||||
@param context: A standard dictionary for contextual values
|
||||
"""
|
||||
analytic_obj = self.pool.get('account.analytic.line')
|
||||
data = context and context.get('active_ids', [])
|
||||
for analytic in analytic_obj.browse(cr, uid, data, context=context):
|
||||
if analytic.invoice_id:
|
||||
raise osv.except_osv(_('Warning !'), _("Invoice is already linked to some of the analytic line(s)!"))
|
||||
|
||||
def do_create(self, cr, uid, ids, context=None):
|
||||
data = self.read(cr, uid, ids, [], context=context)[0]
|
||||
invs = self.pool.get('account.analytic.line').invoice_cost_create(cr, uid, context['active_ids'], data, context=context)
|
||||
mod_obj = self.pool.get('ir.model.data')
|
||||
act_obj = self.pool.get('ir.actions.act_window')
|
||||
|
||||
mod_ids = mod_obj.search(cr, uid, [('name', '=', 'action_invoice_tree1')], context=context)[0]
|
||||
res_id = mod_obj.read(cr, uid, mod_ids, ['res_id'], context=context)['res_id']
|
||||
act_win = act_obj.read(cr, uid, res_id, [], context=context)
|
||||
act_win['domain'] = [('id','in',invoices),('type','=','out_invoice')]
|
||||
act_win['domain'] = [('id','in',invs),('type','=','out_invoice')]
|
||||
act_win['name'] = _('Invoices')
|
||||
return act_win
|
||||
|
||||
|
|
|
@ -1,48 +1,50 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="view_hr_timesheet_invoice_create_final" model="ir.ui.view">
|
||||
<field name="name">hr.timesheet.invoice.create.final.form</field>
|
||||
<field name="model">hr.timesheet.invoice.create.final</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Final invoice for analytic account">
|
||||
<group col="4" colspan="6">
|
||||
<separator string="Do you want details for each line of the invoices ?" colspan="4"/>
|
||||
<field name="date"/>
|
||||
<field name="time"/>
|
||||
<field name="name"/>
|
||||
<field name="price"/>
|
||||
<separator string="Invoice Balance amount" colspan="4"/>
|
||||
<field name="balance_product" required="1"/>
|
||||
</group>
|
||||
<separator colspan="4"/>
|
||||
<group col="2" colspan="4">
|
||||
<button special="cancel" string="Cancel" icon='gtk-cancel'/>
|
||||
<button name="do_create" string="Create Invoices" colspan="1" type="object" icon="terp-gtk-go-back-rtl"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_hr_timesheet_invoice_create_final" model="ir.actions.act_window">
|
||||
<field name="name">Final Invoice</field>
|
||||
<field name="name">Create Invoice</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">hr.timesheet.invoice.create.final</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="multi" eval="1"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.values" id="hr_timesheet_invoice_create_final_values">
|
||||
<record model="ir.values" id="hr_timesheet_invoice_create_final_values">
|
||||
<field name="model_id" ref="model_account_analytic_account" />
|
||||
<field name="name">Final Invoice</field>
|
||||
<field name="object" eval="1" />
|
||||
<field name="name">Invoice</field>
|
||||
<field name="key2">client_action_multi</field>
|
||||
<field name="value" eval="'ir.actions.act_window,' + str(ref('action_hr_timesheet_invoice_create_final'))" />
|
||||
<field name="key">action</field>
|
||||
<field name="model">account.analytic.account</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
|
||||
<record id="view_hr_timesheet_invoice_create_final" model="ir.ui.view">
|
||||
<field name="name">hr.timesheet.invoice.create.final.form</field>
|
||||
<field name="model">hr.timesheet.invoice.create.final</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Invoice contract">
|
||||
<group col="4" colspan="6">
|
||||
<separator string="Do you want to display work details on the invoice ?" colspan="4"/>
|
||||
<field name="date"/>
|
||||
<field name="time"/>
|
||||
<field name="name"/>
|
||||
<field name="price"/>
|
||||
<separator string="Force to use a special product" colspan="4" groups="base.group_extended"/>
|
||||
<field name="product" groups="base.group_extended"/>
|
||||
</group>
|
||||
<separator colspan="4"/>
|
||||
<group col="2" colspan="4">
|
||||
<button special="cancel" string="Cancel" icon='gtk-cancel'/>
|
||||
<button name="do_create" string="Create Invoice" colspan="1" type="object" icon="terp-gtk-go-back-rtl"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
<newline/>
|
||||
<separator colspan="4"/>
|
||||
<group col="9" colspan="8">
|
||||
<field name="state" widget="statusbar" statusbar_visible="open,close" statusbar_colors='{"pending":"blue"}' select="1" readonly="1"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="open,close" statusbar_colors='{"pending":"blue"}' select="1" readonly="1"/>
|
||||
<button name="set_cancel" string="Cancel" type="object" states="open,pending" icon="gtk-cancel"/>
|
||||
<button name="set_template" string="Set as Template" type="object" states="open" icon="gtk-convert" groups="base.group_extended"/>
|
||||
<button name="set_open" string="Reactivate Project" type="object" states="pending,cancelled,close" icon="gtk-ok"/>
|
||||
|
|
|
@ -34,7 +34,7 @@ the Timesheet line entries for particular date and particular user with the eff
|
|||
'author': 'OpenERP SA',
|
||||
'website': 'http://www.openerp.com',
|
||||
'images': ['images/invoice_task_work.jpeg', 'images/my_timesheet.jpeg', 'images/working_hour.jpeg'],
|
||||
'depends': ['project', 'hr_timesheet_sheet', 'hr_timesheet_invoice'],
|
||||
'depends': ['project', 'hr_timesheet_sheet', 'hr_timesheet_invoice', 'account_analytic_analysis'],
|
||||
'init_xml': ['project_timesheet_data.xml'],
|
||||
'update_xml': ["security/ir.model.access.csv","process/project_timesheet_process.xml", "report/task_report_view.xml", "project_timesheet_view.xml"],
|
||||
'demo_xml': ["project_timesheet_demo.xml"],
|
||||
|
|
|
@ -105,8 +105,9 @@ the project form.</field>
|
|||
<!--
|
||||
Time Tracking menu in project Management
|
||||
-->
|
||||
<menuitem icon="terp-project" id="base.menu_main_pm" name="Project" sequence="10"/>
|
||||
<menuitem id="menu_project_working_hours" parent="base.menu_project_management_time_tracking" action="hr_timesheet.act_hr_timesheet_line_evry1_all_form"/>
|
||||
|
||||
<menuitem id="menu_invoicing" name="Billing" parent="base.menu_main_pm" sequence="4"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -489,8 +489,8 @@
|
|||
src_model="product.product"
|
||||
groups="base.group_sale_salesman"/>
|
||||
|
||||
<menuitem id="menu_invoiced" name="Invoicing" parent="base.menu_base_partner" sequence="5" groups="-base.group_extended"/>
|
||||
<menuitem id="menu_invoicing_sales_order_lines" parent="menu_invoiced" action="action_order_line_tree2" sequence="2" groups="base.group_no_one"/>
|
||||
<menuitem id="base.menu_invoiced" name="Invoicing" parent="base.menu_base_partner" sequence="5" groups="-base.group_extended"/>
|
||||
<menuitem id="menu_invoicing_sales_order_lines" parent="base.menu_invoiced" action="action_order_line_tree2" sequence="2" groups="base.group_no_one"/>
|
||||
|
||||
<!-- configartion view -->
|
||||
|
||||
|
|
Loading…
Reference in New Issue