[MERGE] trunk-wms

bzr revid: qdp-launchpad@openerp.com-20140224112005-a7kww6t880xuvz9t
This commit is contained in:
Quentin (OpenERP) 2014-02-24 12:20:05 +01:00
commit 9d41bcbc3a
113 changed files with 7940 additions and 810 deletions

View File

@ -729,8 +729,7 @@ class account_journal(osv.osv):
'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
'entry_posted': fields.boolean('Autopost Created Moves', help='Check this box to automatically post entries of this journal. Note that legally, some entries may be automatically posted when the source document is validated (Invoices), whatever the status of this field.'),
'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"),
'allow_date':fields.boolean('Check Date in Period', help= 'If set to True then do not accept the entry if the entry date is not into the period dates'),
'allow_date':fields.boolean('Check Date in Period', help= 'If checked, the entry won\'t be created if the entry date is not included into the selected period'),
'profit_account_id' : fields.many2one('account.account', 'Profit Account'),
'loss_account_id' : fields.many2one('account.account', 'Loss Account'),
'internal_account_id' : fields.many2one('account.account', 'Internal Transfers Account', select=1),

View File

@ -495,6 +495,7 @@ class account_bank_statement(osv.osv):
ctx = (context or {}).copy()
ctx['journal_id'] = self.browse(cr, uid, ids[0], context=context).journal_id.id
return {
'name': _('Journal Items'),
'view_type':'form',
'view_mode':'tree',
'res_model':'account.move.line',

View File

@ -125,7 +125,7 @@
<field name="journal_id" invisible="1"/>
<field name="period_id" invisible="1" groups="account.group_account_user"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
<field name="user_id"/>
<field name="user_id" string="Responsible"/>
<field name="date_due"/>
<field name="origin"/>
<field name="currency_id" groups="base.group_multi_currency"/>
@ -251,7 +251,7 @@
<group>
<group>
<field domain="[('partner_id', '=', partner_id)]" name="partner_bank_id" on_change="onchange_partner_bank(partner_bank_id)"/>
<field name="user_id" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'account.group_account_invoice']}"/>
<field name="user_id" string="Responsible" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'account.group_account_invoice']}"/>
<field name="name" invisible="1"/>
<field name="payment_term" widget="selection"/>
</group>

View File

@ -1270,12 +1270,11 @@
<field name="model">account.move</field>
<field name="arch" type="xml">
<form string="Account Entry" version="7.0">
<header>
<button name="button_validate" states="draft" string="Post" type="object" class="oe_highlight" groups="account.group_account_invoice"/>
<button name="button_cancel" states="posted" string="Cancel Entry" type="object" groups="account.group_account_invoice"/>
<field name="state" widget="statusbar"/>
</header>
<sheet string="Journal Entries" >
<header>
<button name="button_validate" states="draft" string="Post" type="object" class="oe_highlight" groups="account.group_account_invoice"/>
<button name="button_cancel" states="posted" string="Cancel Entry" type="object" groups="account.group_account_invoice"/>
<field name="state" widget="statusbar"/>
</header>
<label for="name" class="oe_edit_only" attrs="{'invisible':[('name','=','/')]}"/>
<h1>
<field name="name" readonly="True" attrs="{'invisible':[('name','=','/')]}"/>
@ -1384,7 +1383,6 @@
<field name="narration" colspan="4" placeholder="Add an internal note..." nolabel="1" height="50"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
@ -2263,7 +2261,7 @@
<group>
<group>
<field name="journal_id" on_change="onchange_journal_id(journal_id)" widget="selection" domain="[('type', '=', 'cash')]" />
<field name="user_id" readonly="1" string="Responsible"/>
<field name="user_id" attrs="{'readonly':[('state','!=','draft')]}" string="Responsible"/>
<field name='company_id' widget="selection" groups="base.group_multi_company" />
</group>
<group>

View File

@ -10,7 +10,7 @@
<label for="fiscalyear"/>
<div>
<field name="fiscalyear" on_change="onchange_fiscalyear(fiscalyear)" class="oe_inline"/>
<label align="0.7" string="(If you do not select Fiscal year it will take all open fiscal years)" class="oe_inline"/>
<label align="0.7" string="(If you do not select a specific fiscal year, all open fiscal years will be selected.)" class="oe_inline"/>
</div>
<field name="target_move"/>
<label for="period_from" string="Periods"/>

View File

@ -11,7 +11,7 @@
<label for="period_id"/>
<div>
<field name="period_id" class="oe_inline"/>
<label string="(If you do not select period it will take all open periods)" class="oe_inline"/>
<label string="(If you do not select a specific period, all open periods will be selected)" class="oe_inline"/>
</div>
<field name="target_move"/>
</group>

View File

@ -20,10 +20,10 @@
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_form"/>
<field name="arch" type="xml">
<xpath expr="/form/sheet/notebook/page/field[@name='line_id']/tree/field[@name='analytic_account_id']" position="replace">
<xpath expr="/form/notebook/page/field[@name='line_id']/tree/field[@name='analytic_account_id']" position="replace">
<field name="analytics_id" context="{'journal_id':parent.journal_id}" groups="analytic.group_analytic_accounting"/>
</xpath>
<xpath expr="/form/sheet/notebook/page/field[@name='line_id']/form/notebook/page/group/group/field[@name='analytic_account_id']" position="replace">
<xpath expr="/form/notebook/page/field[@name='line_id']/form/notebook/page/group/group/field[@name='analytic_account_id']" position="replace">
<field name="analytics_id" context="{'journal_id':parent.journal_id}" groups="analytic.group_analytic_accounting"/>
</xpath>
</field>

View File

@ -103,7 +103,7 @@
I configure the product with required accounts, and cost method = standard
-
!python {model: product.product}: |
self.write(cr, uid, [ref('product.product_product_3')], {'list_price': 20.00,'standard_price': 9,'categ_id': ref('product.product_category_4'),'valuation': 'real_time',
self.write(cr, uid, [ref('product.product_product_10')], {'list_price': 20.00,'standard_price': 9,'categ_id': ref('product.product_category_4'),'valuation': 'real_time',
'property_account_income': ref('account_anglo_income'),'property_account_expense': ref('account_anglo_cogs'),
'property_account_creditor_price_difference': ref('account_anglo_price_difference'),'property_stock_account_input': ref('account_anglo_stock_input'),
'property_stock_account_output': ref('account_anglo_stock_output'), 'cost_method': 'standard'})
@ -115,7 +115,7 @@
location_id: stock.stock_location_stock
pricelist_id: 1
order_line:
- product_id: product.product_product_3
- product_id: product.product_product_10
product_qty: 1
price_unit: 10
date_planned: !eval "'%s' % (time.strftime('%Y-%m-%d'))"
@ -194,7 +194,7 @@
move_lines:
- company_id: base.main_company
location_id: stock.stock_location_stock
product_id: product.product_product_3
product_id: product.product_product_10
product_uom_qty: 1.0
product_uom: product.product_uom_unit
location_dest_id: stock.stock_location_customers

View File

@ -352,6 +352,7 @@ class account_asset_asset(osv.osv):
context = {}
context.update({'search_default_asset_id': ids, 'default_asset_id': ids})
return {
'name': _('Journal Items'),
'view_type': 'form',
'view_mode': 'tree,form',
'res_model': 'account.move.line',

View File

@ -26,7 +26,7 @@ class account_journal(osv.osv):
_columns = {
'allow_check_writing': fields.boolean('Allow Check writing', help='Check this if the journal is to be used for writing checks.'),
'use_preprint_check': fields.boolean('Use Preprinted Check'),
'use_preprint_check': fields.boolean('Use Preprinted Check', help='Check if you use a preformated sheet for check'),
}

View File

@ -8,10 +8,10 @@
<field name="arch" type="xml">
<form string="Populate Statement:" version="7.0">
<group>
<field name="lines"/>
<field name="lines" nolabel="1"/>
</group>
<footer>
<button name="populate_statement" string="ADD" type="object" class="oe_highlight"/>
<button name="populate_statement" string="Ok" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel"/>
</footer>

View File

@ -80,7 +80,7 @@
<field name="tax_amount" nolabel="1"/>
<div class="oe_subtotal_footer_separator">
<label for="amount"/>
<button type="object" icon="terp-stock_format-scientific" name="compute_tax" class="oe_link oe_edit_only" string="(Update)" attrs="{'invisible': [('state','!=','draft')]}"/>
<button type="object" name="compute_tax" class="oe_link oe_edit_only" string="(Update)" attrs="{'invisible': [('state','!=','draft')]}"/>
</div>
<field name="amount" class="oe_subtotal_footer_separator" nolabel="1"/>
</group>

View File

@ -253,7 +253,7 @@ class account_analytic_account(osv.osv):
def check_recursion(self, cr, uid, ids, context=None, parent=None):
return super(account_analytic_account, self)._check_recursion(cr, uid, ids, context=context, parent=parent)
_order = 'name asc'
_order = 'code, name asc'
_constraints = [
(check_recursion, 'Error! You cannot create recursive analytic accounts.', ['parent_id']),
]

View File

@ -9,7 +9,7 @@
<field name="name">Meeting Types Tree</field>
<field name="model">calendar.event.type</field>
<field name="arch" type="xml">
<tree string="Meeting Types" editable="bottom">
<tree string="Meeting Types">
<field name="name"/>
</tree>
</field>

View File

@ -49,9 +49,7 @@
}
</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a new opportunity.
</p><p>
<p>
OpenERP helps you keep track of your sales pipeline to follow
up potential sales and better forecast your future revenues.
</p><p>

View File

@ -30,7 +30,7 @@
<field name="name">Direct Sales</field>
<field name="code">DM</field>
<field name="use_leads">True</field>
<field name="alias_name">info</field>
<field name="alias_name">sales</field>
<field name="member_ids" eval="[(4, ref('base.user_root'))]"/>
</record>
@ -56,6 +56,14 @@
<field name="section_id" ref="crm.section_sales_department"/>
</record>
<!--default alias for leads-->
<record id="mail_alias_lead_info" model="mail.alias">
<field name="alias_name">info</field>
<field name="alias_model_id" ref="model_crm_lead"/>
<field name="alias_user_id" ref="base.user_root"/>
<field name="alias_parent_model_id" ref="model_crm_case_section"/>
</record>
<!-- notify all employees of module installation -->
<record model="mail.message" id="module_install_notification">
<field name="model">mail.group</field>

View File

@ -950,6 +950,14 @@ class crm_lead(format_address, osv.osv):
default['stage_id'] = self._get_default_stage_id(cr, uid, local_context)
return super(crm_lead, self).copy(cr, uid, id, default, context=context)
def get_empty_list_help(self, cr, uid, help, context=None):
context['empty_list_help_model'] = 'crm.case.section'
context['empty_list_help_id'] = context.get('default_section_id', None)
context['empty_list_help_document_name'] = _("opportunity")
if context.get('default_type') == 'lead':
context['empty_list_help_document_name'] = _("lead")
return super(crm_lead, self).get_empty_list_help(cr, uid, help, context=context)
# ----------------------------------------
# Mail Gateway
# ----------------------------------------

View File

@ -44,9 +44,7 @@
<field name="view_id" eval="False"/>
<field name="search_view_id" ref="crm.view_crm_case_opportunities_filter"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a new opportunity.
</p><p>
<p>
OpenERP helps you keep track of your sales pipeline to follow
up potential sales and better forecast your future revenues.
</p><p>

View File

@ -264,14 +264,16 @@ class crm_phonecall(osv.osv):
Open meeting's calendar view to schedule a meeting on current phonecall.
:return dict: dictionary value for created meeting view
"""
partner_ids = []
phonecall = self.browse(cr, uid, ids[0], context)
if phonecall.partner_id and phonecall.partner_id.email:
partner_ids.append(phonecall.partner_id.id)
res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'calendar', 'action_calendar_event', context)
res['context'] = {
'default_phonecall_id': phonecall.id,
'default_partner_id': phonecall.partner_id and phonecall.partner_id.id or False,
'default_partner_ids': partner_ids,
'default_user_id': uid,
'default_email_from': phonecall.email_from,
'default_state': 'open',
'default_name': phonecall.name,
}
return res

View File

@ -58,7 +58,7 @@
<field name="view_mode">tree,calendar</field>
<field name="view_id" ref="crm_case_inbound_phone_tree_view"/>
<field name="domain">[]</field>
<field name="context">{'default_state': 'done'}</field>
<field name="context">{}</field>
<field name="search_view_id" ref="crm.view_crm_case_phonecalls_filter"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
@ -106,7 +106,7 @@
<field name="view_mode">tree,calendar</field>
<field name="view_id" ref="crm_case_phone_tree_view"/>
<field name="domain">[('state','!=','done')]</field>
<field name="context" eval="'{\'default_state\':\'open\'}'"/>
<field name="context">{}</field>
<field name="search_view_id" ref="crm.view_crm_case_phonecalls_filter"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">

View File

@ -19,6 +19,7 @@
#
##############################################################################
from openerp import SUPERUSER_ID
from openerp.osv import fields, osv
@ -66,7 +67,42 @@ class crm_configuration(osv.TransientModel):
'group_multi_salesteams': fields.boolean("Organize Sales activities into multiple Sales Teams",
implied_group='base.group_multi_salesteams',
help="""Allows you to use Sales Teams to manage your leads and opportunities."""),
'alias_prefix': fields.char('Default Alias Name for Leads'),
'alias_domain' : fields.char('Alias Domain'),
}
_defaults = {
'alias_domain': lambda self, cr, uid, context: self.pool['mail.alias']._get_alias_domain(cr, SUPERUSER_ID, [1], None, None)[1],
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
def _find_default_lead_alias_id(self, cr, uid, context=None):
alias_id = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'crm.mail_alias_lead_info')
if not alias_id:
alias_ids = self.pool['mail.alias'].search(
cr, uid, [
('alias_model_id.model', '=', 'crm.lead'),
('alias_force_thread_id', '=', False),
('alias_parent_model_id.model', '=', 'crm.case.section'),
('alias_parent_thread_id', '=', False),
('alias_defaults', '=', '{}')
], context=context)
alias_id = alias_ids and alias_ids[0] or False
return alias_id
def get_default_alias_prefix(self, cr, uid, ids, context=None):
alias_name = False
alias_id = self._find_default_lead_alias_id(cr, uid, context=context)
if alias_id:
alias_name = self.pool['mail.alias'].browse(cr, uid, alias_id, context=context).alias_name
return {'alias_prefix': alias_name}
def set_default_alias_prefix(self, cr, uid, ids, context=None):
mail_alias = self.pool['mail.alias']
for record in self.browse(cr, uid, ids, context=context):
alias_id = self._find_default_lead_alias_id(cr, uid, context=context)
if not alias_id:
create_ctx = dict(context, alias_model_name='crm.lead', alias_parent_model_name='crm.case.section')
alias_id = self.pool['mail.alias'].create(cr, uid, {'alias_name': record.alias_prefix}, context=create_ctx)
else:
mail_alias.write(cr, uid, alias_id, {'alias_name': record.alias_prefix}, context=context)
return True

View File

@ -7,6 +7,7 @@
<field name="model">sale.config.settings</field>
<field name="inherit_id" ref="base_setup.view_sale_config_settings"/>
<field name="arch" type="xml">
<data>
<div name="config_sale" position="before">
<separator string="After-Sale Services"/>
<group>
@ -26,11 +27,31 @@
<group>
<label for="id" string="Manage Sales Teams"/>
<div>
<field name="group_multi_salesteams" class="oe_inline"/>
<label for="group_multi_salesteams"/>
<div>
<field name="group_multi_salesteams" class="oe_inline"/>
<label for="group_multi_salesteams"/>
</div>
</div>
</group>
</div>
<xpath expr="//group[@name='On Mail Client']" position="before">
<group name="default_alias">
<label for="id" string="Leads Email Alias"/>
<div attrs="{'invisible': [('alias_domain', '=', False)]}">
<div>
<field name="alias_prefix" class="oe_inline" attrs="{'required': [('alias_domain', '!=', False)]}"/>
@
<field name="alias_domain" class="oe_inline" readonly="1"/>
</div>
<p>
All emails sent to this address and processed by the mailgateway
will create a new lead.
</p>
</div>
</group>
</xpath>
</data>
</field>
</record>

View File

@ -27,15 +27,13 @@
</h1>
</div>
<group>
<group name="general">
<field name="active"/>
</group>
<group name="general">
<group string="General Information">
<field name="partner_id"/>
<field name="product_id"/>
</group>
<group>
<field name="active"/>
</group>
</group>
<group col="4">
<group string="Pricing Information">
<field name="normal_price" attrs="{'readonly':[('use_detailed_pricelist', '=', True)]}"/>
<label for="free_if_more_than"/>
@ -43,10 +41,12 @@
<field name="free_if_more_than" attrs="{'readonly':[('use_detailed_pricelist', '=', True)]}"/>
<field name="amount" attrs="{'required':[('free_if_more_than','&lt;&gt;',False)], 'invisible':[('free_if_more_than','=',False)]}"/>
</div>
</group>
<newline/>
<field name="use_detailed_pricelist"/>
</group>
</group>
<field name="pricelist_ids" attrs="{'invisible':[('use_detailed_pricelist','=',False)]}" mode="tree">
<tree string="Delivery grids">
<field name="sequence"/>
@ -235,13 +235,13 @@
<group>
<field name="carrier_id"/>
<field name="carrier_tracking_ref"/>
<field name="number_of_packages"/>
</group>
<group>
<label for="weight" string="Weight"/>
<div>
<field name="weight" class="oe_inline"/>
<field name="weight_uom_id" nolabel="1" class="oe_inline"/>
<field name="number_of_packages"/>
</div>
<field name="weight_net" groups="base.group_no_one"/>
</group>

1912
addons/fleet/i18n/fi.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,114 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * gamification_sale_crm
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 8.0alpha1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-02-13 15:09+0000\n"
"PO-Revision-Date: 2014-02-13 15:09+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: gamification_sale_crm
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_lead_delay_close
msgid "Days to Close a Deal"
msgstr ""
#. module: gamification_sale_crm
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_nbr_new_leads
msgid "New Leads"
msgstr ""
#. module: gamification_sale_crm
#: model:gamification.goal.definition,suffix:gamification_sale_crm.definition_crm_nbr_call
msgid "calls"
msgstr ""
#. module: gamification_sale_crm
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_lead_delay_open
msgid "Time to Qualify a Lead"
msgstr ""
#. module: gamification_sale_crm
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_nbr_sale_order_created
msgid "New Sales Orders"
msgstr ""
#. module: gamification_sale_crm
#: model:gamification.goal.definition,suffix:gamification_sale_crm.definition_crm_nbr_customer_refunds
msgid "invoices"
msgstr ""
#. module: gamification_sale_crm
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_nbr_customer_refunds
msgid "Customer Refunds"
msgstr ""
#. module: gamification_sale_crm
#: model:gamification.challenge,name:gamification_sale_crm.challenge_crm_sale
msgid "Monthly Sales Targets"
msgstr ""
#. module: gamification_sale_crm
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_tot_customer_refunds
msgid "Total Customer Refunds"
msgstr ""
#. module: gamification_sale_crm
#: model:gamification.goal.definition,suffix:gamification_sale_crm.definition_crm_nbr_new_opportunities
msgid "opportunities"
msgstr ""
#. module: gamification_sale_crm
#: model:gamification.goal.definition,suffix:gamification_sale_crm.definition_crm_lead_delay_close
#: model:gamification.goal.definition,suffix:gamification_sale_crm.definition_crm_lead_delay_open
msgid "days"
msgstr ""
#. module: gamification_sale_crm
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_nbr_new_opportunities
msgid "New Opportunities"
msgstr ""
#. module: gamification_sale_crm
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_nbr_call
msgid "Logged Calls"
msgstr ""
#. module: gamification_sale_crm
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_nbr_paid_sale_order
msgid "Paid Sales Orders"
msgstr ""
#. module: gamification_sale_crm
#: model:gamification.challenge,name:gamification_sale_crm.challenge_crm_marketing
msgid "Lead Acquisition"
msgstr ""
#. module: gamification_sale_crm
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_tot_paid_sale_order
msgid "Total Paid Sales Orders"
msgstr ""
#. module: gamification_sale_crm
#: model:gamification.goal.definition,suffix:gamification_sale_crm.definition_crm_nbr_new_leads
msgid "leads"
msgstr ""
#. module: gamification_sale_crm
#: model:gamification.goal.definition,suffix:gamification_sale_crm.definition_crm_nbr_paid_sale_order
#: model:gamification.goal.definition,suffix:gamification_sale_crm.definition_crm_nbr_sale_order_created
msgid "orders"
msgstr ""
#. module: gamification_sale_crm
#: model:gamification.goal.definition,name:gamification_sale_crm.definition_crm_tot_invoices
msgid "Total Invoiced"
msgstr ""

View File

@ -21,16 +21,15 @@
import logging
from openerp import tools
from openerp.modules.module import get_module_resource
from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp import tools
from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
class hr_employee_category(osv.osv):
class hr_employee_category(osv.Model):
def name_get(self, cr, uid, ids, context=None):
if not ids:
@ -73,9 +72,9 @@ class hr_employee_category(osv.osv):
]
class hr_job(osv.osv):
class hr_job(osv.Model):
def _no_of_employee(self, cr, uid, ids, name, args, context=None):
def _get_nbr_employees(self, cr, uid, ids, name, args, context=None):
res = {}
for job in self.browse(cr, uid, ids, context=context):
nb_employees = len(job.employee_ids or [])
@ -93,59 +92,81 @@ class hr_job(osv.osv):
return res
_name = "hr.job"
_description = "Job Description"
_inherit = ['mail.thread']
_description = "Job Position"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_columns = {
'name': fields.char('Job Name', size=128, required=True, select=True),
'expected_employees': fields.function(_no_of_employee, string='Total Forecasted Employees',
'expected_employees': fields.function(_get_nbr_employees, string='Total Forecasted Employees',
help='Expected number of employees for this job position after new recruitment.',
store = {
'hr.job': (lambda self,cr,uid,ids,c=None: ids, ['no_of_recruitment'], 10),
'hr.employee': (_get_job_position, ['job_id'], 10),
}, type='integer',
multi='no_of_employee'),
'no_of_employee': fields.function(_no_of_employee, string="Current Number of Employees",
multi='_get_nbr_employees'),
'no_of_employee': fields.function(_get_nbr_employees, string="Current Number of Employees",
help='Number of employees currently occupying this job position.',
store = {
'hr.employee': (_get_job_position, ['job_id'], 10),
}, type='integer',
multi='no_of_employee'),
'no_of_recruitment': fields.integer('Expected in Recruitment', help='Number of new employees you expect to recruit.'),
multi='_get_nbr_employees'),
'no_of_recruitment': fields.integer('Expected New Employees', help='Number of new employees you expect to recruit.'),
'no_of_hired_employee': fields.integer('Hired Employees', help='Number of hired employees for this job position during recruitment phase.'),
'employee_ids': fields.one2many('hr.employee', 'job_id', 'Employees', groups='base.group_user'),
'description': fields.text('Job Description'),
'requirements': fields.text('Requirements'),
'department_id': fields.many2one('hr.department', 'Department'),
'company_id': fields.many2one('res.company', 'Company'),
'state': fields.selection([('open', 'No Recruitment'), ('recruit', 'Recruitement in Progress')], 'Status', readonly=True, required=True,
help="By default 'In position', set it to 'In Recruitment' if recruitment process is going on for this job position."),
'state': fields.selection([('open', 'Recruitment Closed'), ('recruit', 'Recruitment in Progress')],
string='Status', readonly=True, required=True,
track_visibility='always',
help="By default 'Closed', set it to 'In Recruitment' if recruitment process is going on for this job position."),
'write_date': fields.datetime('Update Date', readonly=True),
}
_defaults = {
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'hr.job', context=c),
'no_of_recruitment': 0,
'company_id': lambda self, cr, uid, ctx=None: self.pool.get('res.company')._company_default_get(cr, uid, 'hr.job', context=ctx),
'state': 'open',
}
_sql_constraints = [
('name_company_uniq', 'unique(name, company_id, department_id)', 'The name of the job position must be unique per department in company!'),
('hired_employee_check', "CHECK ( no_of_hired_employee <= no_of_recruitment )", "Number of hired employee must be less than expected number of employee in recruitment."),
]
def on_change_expected_employee(self, cr, uid, ids, no_of_recruitment, no_of_employee, context=None):
if context is None:
context = {}
return {'value': {'expected_employees': no_of_recruitment + no_of_employee}}
def job_recruitement(self, cr, uid, ids, *args):
for job in self.browse(cr, uid, ids):
def set_recruit(self, cr, uid, ids, context=None):
for job in self.browse(cr, uid, ids, context=context):
no_of_recruitment = job.no_of_recruitment == 0 and 1 or job.no_of_recruitment
self.write(cr, uid, [job.id], {'state': 'recruit', 'no_of_recruitment': no_of_recruitment})
self.write(cr, uid, [job.id], {'state': 'recruit', 'no_of_recruitment': no_of_recruitment}, context=context)
return True
def job_open(self, cr, uid, ids, *args):
self.write(cr, uid, ids, {'state': 'open', 'no_of_recruitment': 0})
def set_open(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {
'state': 'open',
'no_of_recruitment': 0,
'no_of_hired_employee': 0
}, context=context)
return True
def copy(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
default.update({
'employee_ids': [],
'no_of_recruitment': 0,
'no_of_hired_employee': 0,
})
if 'name' in default:
job = self.browse(cr, uid, id, context=context)
default['name'] = _("%s (copy)") % (job.name)
return super(hr_job, self).copy(cr, uid, id, default=default, context=context)
# ----------------------------------------
# Compatibility methods
# ----------------------------------------
_no_of_employee = _get_nbr_employees # v7 compatibility
job_open = set_open # v7 compatibility
job_recruitment = set_recruit # v7 compatibility
class hr_employee(osv.osv):
_name = "hr.employee"

View File

@ -45,7 +45,7 @@
</group>
<group string="Position">
<field name="department_id" on_change="onchange_department_id(department_id)"/>
<field name="job_id" options='{"no_open": True}' domain="[('state','!=','old')]" context="{'form_view_ref': 'hr.view_hr_job_employee_form'}"/>
<field name="job_id"/>
<field name="parent_id"/>
<field name="coach_id"/>
</group>
@ -333,8 +333,8 @@
<field name="arch" type="xml">
<form string="Job" version="7.0">
<header>
<button name="job_recruitement" string="Launch Recruitement" states="open" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="job_open" string="Stop Recruitment" states="recruit" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="set_recruit" string="Launch Recruitment" states="open" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="set_open" string="Stop Recruitment" states="recruit" type="object" class="oe_highlight" groups="base.group_user"/>
<field name="state" widget="statusbar" statusbar_visible="recruit,open"/>
</header>
<sheet>
@ -342,20 +342,20 @@
<label for="name" class="oe_edit_only"/>
<h1><field name="name" class="oe_inline"/></h1>
</div>
<group>
<group name="job_data">
<field name="no_of_employee" groups="base.group_user"/>
<field name="no_of_recruitment" on_change="on_change_expected_employee(no_of_recruitment,no_of_employee)"/>
<field name="expected_employees" groups="base.group_user"/>
<field name="company_id" widget="selection" groups="base.group_multi_company"/>
<field name="department_id"/>
</group>
<div class="oe_right" name="buttons"/>
<group name="employee_data">
<field name="department_id" class="oe_inline"/>
<label for="no_of_employee"/>no_of_recruitment
<div>
<field name="no_of_employee" class="oe_inline"/>
<p><field name="no_of_recruitment" groups="base.group_user" colspan="0" class="oe_inline" style="padding-top: 1px"/> new employee(s) expected</p>
</div>
</group>
<div>
<div attrs="{'invisible': [('state', '!=', 'recruit')]}">
<label for="description"/>
<field name="description"/>
</div>
<div>
<div attrs="{'invisible': [('state', '!=', 'recruit')]}">
<label for="requirements"/>
<field name="requirements"/>
</div>
@ -378,6 +378,7 @@
<field name="no_of_employee"/>
<field name="no_of_recruitment"/>
<field name="expected_employees"/>
<field name="no_of_hired_employee"/>
<field name="state"/>
</tree>
</field>
@ -389,34 +390,18 @@
<field name="arch" type="xml">
<search string="Jobs">
<field name="name" string="Job"/>
<filter icon="terp-camera_test" domain="[('state','=','open')]" string="In Position" help="In Position"/>
<filter icon="terp-personal+" domain="[('state','=','recruit')]" string="In Recruitment" help="In Recruitment"/>
<filter domain="[('state','=','open')]" string="In Position"/>
<filter domain="[('state','=','recruit')]" string="In Recruitment" name="in_recruitment"/>
<field name="department_id"/>
<group expand="0" string="Group By...">
<filter string="Department" icon="terp-personal+" domain="[]" context="{'group_by':'department_id'}"/>
<filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}"/>
<filter string="Company" icon="terp-go-home" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<filter string="Department" domain="[]" context="{'group_by':'department_id'}"/>
<filter string="Status" domain="[]" context="{'group_by':'state'}"/>
<filter string="Company" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
</group>
</search>
</field>
</record>
<record id="view_hr_job_employee_form" model="ir.ui.view">
<field name="name">hr.job.employee.form</field>
<field name="model">hr.job</field>
<field name="priority">20</field>
<field name="arch" type="xml">
<form string="Job" version="7.0">
<group col="4">
<field name="name"/>
<field name="department_id"/>
</group>
<label for="description"/>
<field name="description"/>
</form>
</field>
</record>
<record model="ir.actions.act_window" id="action_hr_job">
<field name="name">Job Positions</field>
<field name="res_model">hr.job</field>
@ -441,7 +426,6 @@
</record>
<menuitem name="Recruitment" id="base.menu_crm_case_job_req_main" parent="menu_hr_root" groups="base.group_hr_user"/>
<menuitem parent="hr.menu_hr_configuration" id="menu_hr_job" action="action_hr_job" sequence="6"/>
<!-- hr.department -->
<record id="view_department_form" model="ir.ui.view">

View File

@ -35,7 +35,7 @@
<group name="recruitment_grp">
<label for="id" string="Talent Management"/>
<div name="recruitment">
<div>
<div name="hr_recruitment">
<field name="module_hr_recruitment" class="oe_inline"/>
<label for="module_hr_recruitment"/>
</div>

View File

@ -20,6 +20,7 @@
name: HR Officer
login: hro
password: hro
email: hro@example.com
-
I added groups for HR Officer.
-

View File

@ -15,10 +15,10 @@
- state == 'open'
- no_of_recruitment == 0
-
Now, Recruitement is started so I start recruitement of Job Postion of "Developer" Profile.
Now, Recruitment is started so I start recruitment of Job Postion of "Developer" Profile.
-
!python {model: hr.job}: |
self.job_recruitement(cr, uid, [ref('job_developer')])
self.job_recruitment(cr, uid, [ref('job_developer')])
-
I check 'state' and number of 'Expected in Recruitment' after initiating the recruitment
-

View File

@ -81,7 +81,7 @@
<group>
<group>
<field name="employee_id" on_change="onchange_employee_id(employee_id)"/>
<field name="job_id" context="{'form_view_ref': 'hr.view_hr_job_employee_form'}"/>
<field name="job_id"/>
</group>
<group>
<field name="type_id"/>

View File

@ -0,0 +1,191 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * hr_gamification
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 8.0alpha1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-02-13 15:10+0000\n"
"PO-Revision-Date: 2014-02-13 15:10+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: hr_gamification
#: view:hr.employee:0
msgid "Grant a Badge"
msgstr ""
#. module: hr_gamification
#: model:ir.actions.act_window,help:hr_gamification.goals_menu_groupby_action2
msgid "<p class=\"oe_view_nocontent_create\">\n"
" Click to create a goal. \n"
" </p>\n"
" <p>\n"
" A goal is defined by a user and a goal type.\n"
" Goals can be created automatically by using challenges.\n"
" </p>\n"
" "
msgstr ""
#. module: hr_gamification
#: model:ir.ui.menu,name:hr_gamification.menu_hr_gamification
msgid "Engagement"
msgstr ""
#. module: hr_gamification
#: view:gamification.badge.user.wizard:0
msgid "Reward Employee with"
msgstr ""
#. module: hr_gamification
#: code:addons/hr_gamification/wizard/grant_badge.py:45
#, python-format
msgid "You can send badges only to employees linked to a user."
msgstr ""
#. module: hr_gamification
#: view:gamification.badge.user.wizard:0
#: model:ir.actions.act_window,name:hr_gamification.action_reward_wizard
msgid "Reward Employee"
msgstr ""
#. module: hr_gamification
#: field:gamification.badge.user,employee_id:0
#: field:gamification.badge.user.wizard,employee_id:0
#: model:ir.model,name:hr_gamification.model_hr_employee
msgid "Employee"
msgstr ""
#. module: hr_gamification
#: model:ir.model,name:hr_gamification.model_gamification_badge
msgid "Gamification badge"
msgstr ""
#. module: hr_gamification
#: model:ir.model,name:hr_gamification.model_gamification_badge_user_wizard
msgid "gamification.badge.user.wizard"
msgstr ""
#. module: hr_gamification
#: view:hr.employee:0
msgid "to reward this employee for a good action"
msgstr ""
#. module: hr_gamification
#: model:ir.actions.act_window,name:hr_gamification.goals_menu_groupby_action2
#: model:ir.ui.menu,name:hr_gamification.gamification_goal_menu_hr
msgid "Goals History"
msgstr ""
#. module: hr_gamification
#: field:hr.employee,badge_ids:0
msgid "Employee Badges"
msgstr ""
#. module: hr_gamification
#: code:addons/hr_gamification/wizard/grant_badge.py:48
#, python-format
msgid "You can not send a badge to yourself"
msgstr ""
#. module: hr_gamification
#: view:gamification.badge.user.wizard:0
msgid "Describe what they did and why it matters (will be public)"
msgstr ""
#. module: hr_gamification
#: view:hr.employee:0
msgid "Click to grant this employee his first badge"
msgstr ""
#. module: hr_gamification
#: model:ir.actions.act_window,help:hr_gamification.challenge_list_action2
msgid "<p class=\"oe_view_nocontent_create\">\n"
" Click to create a challenge. \n"
" </p>\n"
" <p>\n"
" Assign a list of goals to chosen users to evaluate them.\n"
" The challenge can use a period (weekly, monthly...) for automatic creation of goals.\n"
" The goals are created for the specified users or member of the group.\n"
" </p>\n"
" "
msgstr ""
#. module: hr_gamification
#: view:hr.employee:0
msgid "Goals"
msgstr ""
#. module: hr_gamification
#: view:gamification.badge.user.wizard:0
msgid "What are you thank for?"
msgstr ""
#. module: hr_gamification
#: constraint:gamification.badge.user:0
msgid "The selected employee does not correspond to the selected user."
msgstr ""
#. module: hr_gamification
#: field:hr.employee,has_badges:0
msgid "Has Badges"
msgstr ""
#. module: hr_gamification
#: view:hr.employee:0
msgid "Badges are rewards of good work. Give them to people you believe deserve it."
msgstr ""
#. module: hr_gamification
#: view:hr.employee:0
msgid "Received Badges"
msgstr ""
#. module: hr_gamification
#: field:hr.employee,goal_ids:0
msgid "Employee HR Goals"
msgstr ""
#. module: hr_gamification
#: model:ir.model,name:hr_gamification.model_gamification_badge_user
msgid "Gamification user badge"
msgstr ""
#. module: hr_gamification
#: view:gamification.badge:0
msgid "Granted Employees"
msgstr ""
#. module: hr_gamification
#: model:ir.actions.act_window,name:hr_gamification.challenge_list_action2
#: model:ir.ui.menu,name:hr_gamification.gamification_challenge_menu_hr
msgid "Challenges"
msgstr ""
#. module: hr_gamification
#: code:addons/hr_gamification/wizard/grant_badge.py:45
#: code:addons/hr_gamification/wizard/grant_badge.py:48
#, python-format
msgid "Warning!"
msgstr ""
#. module: hr_gamification
#: view:gamification.badge.user.wizard:0
msgid "Cancel"
msgstr ""
#. module: hr_gamification
#: view:gamification.badge.user.wizard:0
msgid "or"
msgstr ""
#. module: hr_gamification
#: model:ir.ui.menu,name:hr_gamification.gamification_badge_menu_hr
msgid "Badges"
msgstr ""

View File

@ -37,13 +37,14 @@ You can define the different phases of interviews and easily rate the applicant
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'images': ['images/hr_recruitment_analysis.jpeg','images/hr_recruitment_applicants.jpeg'],
'images': ['images/hr_recruitment_analysis.jpeg','images/hr_recruitment_applicants.jpeg','static/src/img/down1.png'],
'depends': [
'decimal_precision',
'hr',
'survey',
'calendar',
'fetchmail',
'web_kanban_gauge',
],
'data': [
'wizard/hr_recruitment_create_partner_job_view.xml',
@ -58,7 +59,11 @@ You can define the different phases of interviews and easily rate the applicant
'hr_recruitment_data.xml',
],
'demo': ['hr_recruitment_demo.xml'],
'js': [
'static/src/js/job_position.js',
],
'test': ['test/recruitment_process.yml'],
'css':['static/src/css/job_position.css'],
'installable': True,
'auto_install': False,
'application': True,

View File

@ -19,12 +19,10 @@
#
##############################################################################
from openerp import tools
from datetime import datetime
from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp.tools import html2plaintext
AVAILABLE_PRIORITIES = [
('', ''),
@ -246,7 +244,8 @@ class hr_applicant(osv.Model):
if job_id:
job_record = self.pool.get('hr.job').browse(cr, uid, job_id, context=context)
department_id = job_record and job_record.department_id and job_record.department_id.id or False
return {'value': {'department_id': department_id}}
user_id = job_record and job_record.user_id and job_record.user_id.id or False
return {'value': {'department_id': department_id, 'user_id': user_id}}
def onchange_department_id(self, cr, uid, ids, department_id=False, stage_id=False, context=None):
if not stage_id:
@ -331,19 +330,12 @@ class hr_applicant(osv.Model):
value = self.pool.get("survey").action_print_survey(cr, uid, ids, context=context)
return value
def action_get_attachment_tree_view(self, cr, uid, ids, context):
domain = ['&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', ids)]
return {
'name': _('Attachments'),
'domain': domain,
'res_model': 'ir.attachment',
'type': 'ir.actions.act_window',
'view_id': False,
'view_mode': 'tree,form',
'view_type': 'form',
'limit': 80,
'context': "{'default_res_model': '%s'}" % (self._name)
}
def action_get_attachment_tree_view(self, cr, uid, ids, context=None):
model, action_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'action_attachment')
action = self.pool.get(model).read(cr, uid, action_id, context=context)
action['context'] = {'default_res_model': self._name, 'default_res_id': ids[0]}
action['domain'] = str(['&', ('res_model', '=', self._name), ('res_id', 'in', ids)])
return action
def message_get_suggested_recipients(self, cr, uid, ids, context=None):
recipients = super(hr_applicant, self).message_get_suggested_recipients(cr, uid, ids, context=context)
@ -364,7 +356,7 @@ class hr_applicant(osv.Model):
val = msg.get('from').split('<')[0]
defaults = {
'name': msg.get('subject') or _("No Subject"),
'partner_name':val,
'partner_name': val,
'email_from': msg.get('from'),
'email_cc': msg.get('cc'),
'user_id': False,
@ -378,13 +370,20 @@ class hr_applicant(osv.Model):
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
context['mail_create_nolog'] = True
if vals.get('department_id') and not context.get('default_department_id'):
context['default_department_id'] = vals.get('department_id')
if vals.get('job_id') or context.get('default_job_id'):
job_id = vals.get('job_id') or context.get('default_job_id')
vals.update(self.onchange_job(cr, uid, [], job_id, context=context)['value'])
obj_id = super(hr_applicant, self).create(cr, uid, vals, context=context)
applicant = self.browse(cr, uid, obj_id, context=context)
if applicant.job_id:
self.pool.get('hr.job').message_post(cr, uid, [applicant.job_id.id], body=_('Applicant <b>created</b>'), subtype="hr_recruitment.mt_job_new_applicant", context=context)
name = applicant.partner_name if applicant.partner_name else applicant.name
self.pool['hr.job'].message_post(
cr, uid, [applicant.job_id.id],
body=_('New application from %s') % name,
subtype="hr_recruitment.mt_job_applicant_new", context=context)
return obj_id
def write(self, cr, uid, ids, vals, context=None):
@ -404,6 +403,15 @@ class hr_applicant(osv.Model):
else:
res = super(hr_applicant, self).write(cr, uid, ids, vals, context=context)
# post processing: if job changed, post a message on the job
if vals.get('job_id'):
for applicant in self.browse(cr, uid, ids, context=None):
name = applicant.partner_name if applicant.partner_name else applicant.name
self.pool['hr.job'].message_post(
cr, uid, [vals['job_id']],
body=_('New application from %s') % name,
subtype="hr_recruitment.mt_job_applicant_new", context=context)
# post processing: if stage changed, post a message in the chatter
if vals.get('stage_id'):
stage = self.pool['hr.recruitment.stage'].browse(cr, uid, vals['stage_id'], context=context)
@ -444,7 +452,7 @@ class hr_applicant(osv.Model):
address_id = self.pool.get('res.partner').address_get(cr, uid, [applicant.partner_id.id], ['contact'])['contact']
contact_name = self.pool.get('res.partner').name_get(cr, uid, [applicant.partner_id.id])[0][1]
if applicant.job_id and (applicant.partner_name or contact_name):
applicant.job_id.write({'no_of_recruitment': applicant.job_id.no_of_recruitment - 1})
applicant.job_id.write({'no_of_hired_employee': applicant.job_id.no_of_hired_employee + 1}, context=context)
emp_id = hr_employee.create(cr, uid, {'name': applicant.partner_name or contact_name,
'job_id': applicant.job_id.id,
'address_home_id': address_id,
@ -454,6 +462,10 @@ class hr_applicant(osv.Model):
'work_phone': applicant.department_id and applicant.department_id.company_id and applicant.department_id.company_id.phone or False,
})
self.write(cr, uid, [applicant.id], {'emp_id': emp_id}, context=context)
self.pool['hr.job'].message_post(
cr, uid, [applicant.job_id.id],
body=_('New Employee %s Hired') % applicant.partner_name if applicant.partner_name else applicant.name,
subtype="hr_recruitment.mt_job_applicant_hired", context=context)
else:
raise osv.except_osv(_('Warning!'), _('You must define an Applied Job and a Contact Name for this applicant.'))
@ -490,16 +502,37 @@ class hr_job(osv.osv):
_inherit = "hr.job"
_name = "hr.job"
_inherits = {'mail.alias': 'alias_id'}
def _get_attached_docs(self, cr, uid, ids, field_name, arg, context=None):
res = {}
attachment_obj = self.pool.get('ir.attachment')
for job_id in ids:
applicant_ids = self.pool.get('hr.applicant').search(cr, uid, [('job_id', '=', job_id)], context=context)
res[job_id] = attachment_obj.search(
cr, uid, [
'|',
'&', ('res_model', '=', 'hr.job'), ('res_id', '=', job_id),
'&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', applicant_ids)
], context=context)
return res
_columns = {
'survey_id': fields.many2one('survey', 'Interview Form', help="Choose an interview form for this job position and you will be able to print/answer this interview from all applicants who apply for this job"),
'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True,
help="Email alias for this job position. New emails will automatically "
"create new applicants for this job position."),
'address_id': fields.many2one('res.partner', 'Job Location', help="Address where employees are working"),
'application_ids': fields.one2many('hr.applicant', 'job_id', 'Applications'),
'manager_id': fields.related('department_id', 'manager_id', type='many2one', string='Department Manager', relation='hr.employee', readonly=True, store=True),
'document_ids': fields.function(_get_attached_docs, type='one2many', relation='ir.attachment', string='Applications'),
'user_id': fields.many2one('res.users', 'Recruitment Responsible', track_visibility='onchange'),
'color': fields.integer('Color Index'),
}
def _address_get(self, cr, uid, context=None):
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
return user.company_id.partner_id.id
_defaults = {
'address_id': _address_get
}
@ -541,6 +574,18 @@ class hr_job(osv.osv):
'nodestroy': True,
}
def action_get_attachment_tree_view(self, cr, uid, ids, context=None):
#open attachments of job and related applicantions.
model, action_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'action_attachment')
action = self.pool.get(model).read(cr, uid, action_id, context=context)
applicant_ids = self.pool.get('hr.applicant').search(cr, uid, [('job_id', 'in', ids)], context=context)
action['context'] = {'default_res_model': self._name, 'default_res_id': ids[0]}
action['domain'] = str(['|', '&', ('res_model', '=', 'hr.job'), ('res_id', 'in', ids), '&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', applicant_ids)])
return action
def action_set_no_of_recruitment(self, cr, uid, id, value, context=None):
return self.write(cr, uid, [id], {'no_of_recruitment': value}, context=context)
class applicant_category(osv.osv):
""" Category of applicant """

View File

@ -495,13 +495,9 @@
<field name="alias_name">jobs</field>
<field name="alias_model_id" ref="model_hr_applicant"/>
<field name="alias_user_id" ref="base.user_root"/>
<field name="alias_parent_model_id" ref="model_hr_job"/>
</record>
<!-- Job-related subtypes for messaging / Chatter -->
<record id="mt_job_new_applicant" model="mail.message.subtype">
<field name="name">New Applicant</field>
<field name="res_model">hr.job</field>
</record>
<!-- Applicant-related subtypes for messaging / Chatter -->
<record id="mt_applicant_new" model="mail.message.subtype">
<field name="name">New Applicant</field>
@ -515,12 +511,35 @@
<field name="default" eval="False"/>
<field name="description">Stage changed</field>
</record>
<record id="mt_applicant_employee" model="mail.message.subtype">
<record id="mt_applicant_hired" model="mail.message.subtype">
<field name="name">Applicant Hired</field>
<field name="res_model">hr.applicant</field>
<field name="default" eval="False"/>
<field name="description">Applicant hired</field>
</record>
<!-- Job-related subtypes for messaging / Chatter -->
<record id="mt_job_applicant_new" model="mail.message.subtype">
<field name="name">Applicant Created</field>
<field name="res_model">hr.job</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_applicant_new')"/>
<field name="relation_field">job_id</field>
</record>
<record id="mt_job_applicant_stage_changed" model="mail.message.subtype">
<field name="name">Applicant Stage Changed</field>
<field name="res_model">hr.job</field>
<field name="default" eval="True"/>
<field name="parent_id" eval="ref('mt_applicant_stage_changed')"/>
<field name="relation_field">job_id</field>
</record>
<record id="mt_job_applicant_hired" model="mail.message.subtype">
<field name="name">Applicant Hired</field>
<field name="res_model">hr.job</field>
<field name="default" eval="True"/>
<field name="parent_id" eval="ref('mt_applicant_hired')"/>
<field name="relation_field">job_id</field>
</record>
<!-- Applicant Categories(Tag) -->
<record id="tag_applicant_reserve" model="hr.applicant_category">
<field name="name">Reserve</field>

View File

@ -1,7 +1,6 @@
<?xml version="1.0"?>
<openerp>
<data>
######################## JOB OPPORTUNITIES (menu) ###########################
<record model="ir.actions.act_window" id="crm_case_categ0_act_job">
<field name="name">Applications</field>
@ -9,16 +8,15 @@
<field name="view_mode">kanban,tree,form,graph,calendar</field>
<field name="view_id" eval="False"/>
<field name="search_view_id" ref="view_crm_case_jobs_filter"/>
<field name="context">{'empty_list_help_model': 'hr.job'}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to add a new job applicant.
<p>
OpenERP helps you track applicants in the recruitment
process and follow up all operations: meetings, interviews, etc.
</p><p>
OpenERP helps you track applicants in the recruitment process
and follow up all operations: meetings, interviews, etc.
Candidates and their cv's are automatically created when they
apply for a job. If you install the document management modules,
all resumes are indexed automatically, so that you can easily
search through their content in the recruitment menu.
Applicants and their attached CV are created automatically when an email is sent.
If you install the document management modules, all resumes are indexed automatically,
so that you can easily search through their content.
</p>
</field>
</record>
@ -58,15 +56,10 @@
sequence="1"/>
<!-- ALL JOBS REQUESTS -->
<menuitem parent="base.menu_crm_case_job_req_main" id="hr.menu_hr_job_position" action="action_hr_job" sequence="1"/>
<menuitem
name="Applications"
parent="base.menu_crm_case_job_req_main"
id="menu_crm_case_categ0_act_job" action="crm_case_categ0_act_job" sequence="1"/>
<menuitem parent="hr.menu_hr_configuration" id="hr.menu_hr_job" action="hr.action_hr_job" sequence="2"/>
id="menu_crm_case_categ0_act_job" action="crm_case_categ0_act_job" sequence="2"/>
</data>
</openerp>

View File

@ -34,7 +34,7 @@
</record>
<!-- Jobs -->
<!-- Applicants -->
<record model="ir.ui.view" id="crm_case_tree_view_job">
<field name="name">Applicants</field>
<field name="model">hr.applicant</field>
@ -91,10 +91,10 @@
<label for="partner_name" class="oe_edit_only"/>
<h2 style="display: inline-block;">
<field name="partner_name" class="oe_inline"/>
<button string="Create Employee" name="create_employee_from_applicant" type="object"
class="oe_link oe_inline" style="margin-left: 8px;"
attrs="{'invisible': [('emp_id', '!=', False)]}"/>
</h2>
<button string="Create Employee" name="create_employee_from_applicant" type="object"
class="oe_link oe_inline" style="margin-left: 8px;"
attrs="{'invisible': [('emp_id', '!=', False)]}"/>
</div>
<group>
<group>
@ -307,46 +307,190 @@
</field>
</record>
<!-- HR Job -->
<record model="ir.actions.act_window" id="action_hr_job_applications">
<field name="name">Applications</field>
<field name="res_model">hr.applicant</field>
<field name="view_mode">kanban,tree,form,graph,calendar</field>
<field name="context">{'search_default_job_id': [active_id], 'default_job_id': active_id, 'empty_list_help_model': 'hr.job'}</field>
<field name="help" type="html">
<p>
OpenERP helps you track applicants in the recruitment
process and follow up all operations: meetings, interviews, etc.
</p><p>
Applicants and their attached CV are created automatically when an email is sent.
If you install the document management modules, all resumes are indexed automatically,
so that you can easily search through their content.
</p>
</field>
</record>
<!-- Jobs -->
<record id="view_job_filter_recruitment" model="ir.ui.view">
<field name="name">Job</field>
<field name="model">hr.job</field>
<field name="inherit_id" ref="hr.view_job_filter"/>
<field name="arch" type="xml">
<field name="department_id" positon="after">
<separator/>
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
</field>
</field>
</record>
<record id="hr_job_survey" model="ir.ui.view">
<field name="name">hr.job.form1</field>
<field name="model">hr.job</field>
<field name="inherit_id" ref="hr.view_hr_job_form"/>
<field name="arch" type="xml">
<group name="job_data" position="inside">
<group name="employee_data" position="inside">
<label for="survey_id" groups="base.group_user"/>
<div groups="base.group_user">
<field name="survey_id" class="oe_inline" domain="[('type','=','Human Resources')]"/>
<button string="Print Interview" name="action_print_survey" type="object" attrs="{'invisible':[('survey_id','=',False)]}" class="oe_inline oe_link"/>
</div>
<label for="address_id"/>
<div>
<field name="address_id" context="{'show_address': 1}"/>
<span class="oe_grey">(empty = remote work)</span>
</div>
</group>
<field name="expected_employees" position="after">
<label for="survey_id" groups="base.group_user"/>
<div groups="base.group_user">
<field name="survey_id" class="oe_inline" domain="[('type','=','Human Resources')]"/>
<button class="oe_inline"
string="Interview"
name="action_print_survey" type="object"
attrs="{'invisible':[('survey_id','=',False)]}"/>
</div>
</field>
<xpath expr="//group[@name='job_data']" position="after">
<group name="group_alias"
attrs="{'invisible': [('alias_domain', '=', False)]}">
<label for="alias_name" string="Email Alias"/>
<div name="alias_def">
<xpath expr="//field[@name='department_id']" position="after">
<label for="alias_name" string="Specific Email Address" attrs="{'invisible': [('alias_domain', '=', False)]}" help ="Define a specific contact address for this job position. If you keep it empty, the default email address will be used which is in human resources settings"/>
<div name="alias_def" attrs="{'invisible': [('alias_domain', '=', False)]}">
<field name="alias_id" class="oe_read_only oe_inline"
string="Email Alias" required="0"/>
<div class="oe_edit_only oe_inline" name="edit_alias" style="display: inline;" >
<field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline" readonly="1"/>
</div>
</div>
<field name="alias_contact" class="oe_inline" string="Accept Emails From"/>
</group>
</xpath>
<xpath expr="//field[@name='department_id']" position="after">
<field name="user_id" class="oe_inline"/>
</xpath>
<div name="buttons" position="inside">
<button string="Applications" name="%(action_hr_job_applications)d" context="{'default_user_id': user_id}" type="action"/>
<button string="Documents" name="action_get_attachment_tree_view" type="object"/>
</div>
</field>
</record>
<record id="view_hr_job_kanban" model="ir.ui.view">
<field name="name">hr.job.kanban</field>
<field name="model">hr.job</field>
<field name="arch" type="xml">
<kanban version="7.0" class="oe_background_grey">
<field name="name"/>
<field name="department_id"/>
<field name="no_of_recruitment"/>
<field name="color"/>
<field name="application_ids"/>
<field name="document_ids"/>
<field name="no_of_hired_employee"/>
<field name="manager_id"/>
<field name="survey_id"/>
<field name="state"/>
<field name="user_id"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_job oe_kanban_card oe_kanban_global_click">
<div class="oe_dropdown_toggle oe_dropdown_kanban oe_custom">
<span class="oe_e">í</span>
<ul class="oe_dropdown_menu">
<t t-if="widget.view.is_action_enabled('edit')">
<li><a type="edit">Edit...</a></li>
</t>
<t t-if="widget.view.is_action_enabled('delete')">
<li><a type="delete">Delete</a></li>
</t>
<li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
</ul>
</div>
<div class = "oe_kanban_content">
<t t-if="record.user_id.raw_value">
<img t-att-src="kanban_image('res.users', 'image_medium', record.user_id.raw_value[0])" t-att-title="record.user_id.value" class="oe_kanban_avatar oe_job_avatar"/>
</t>
<t t-if="record.user_id.raw_value === false">
<img t-att-src='_s + "/base/static/src/img/avatar.png"' class="oe_kanban_avatar oe_job_avatar"/>
</t>
<div class="oe_job_detail">
<div class="oe_job oe_name oe_kanban_ellipsis">
<field name="name"/>
</div>
<div class="oe_job oe_department oe_kanban_ellipsis">
<field name="department_id"/>
<span t-if="record.manager_id.value" class="oe_manager_name">
(<t t-esc="record.manager_id.value"/>)
</span>
</div>
<div class="oe_job_alias oe_kanban_ellipsis" t-if=" record.alias_id.value and record.state.raw_value == 'recruit'">
<span class="oe_e">%%</span><small><field name="alias_id"/></small>
</div>
</div>
<t t-if="record.state.raw_value == 'recruit'">
<div class="oe_applications">
<a name="%(action_hr_job_applications)d" type="action">
<span t-if="record.application_ids.raw_value.length gt 1"><t t-esc="record.application_ids.raw_value.length"/> Applications</span>
<span t-if="record.application_ids.raw_value.length lt 2"><t t-esc="record.application_ids.raw_value.length"/> Application</span>
</a>
<br/>
<a t-if="record.document_ids.raw_value.length gt 0" name="action_get_attachment_tree_view" type="object">
<span t-if="record.document_ids.raw_value.length gt 1"><t t-esc="record.document_ids.raw_value.length"/> Documents</span>
<span t-if="record.document_ids.raw_value.length lt 2"><t t-esc="record.document_ids.raw_value.length"/> Document</span>
</a>
</div>
<div class="oe_job_justgage">
<field state="recruit" name="no_of_hired_employee" widget="gauge"
style="width:160px; height: 120px;"
options="{
'max_field': 'no_of_recruitment',
'label': 'Hired Employees',
'on_change': 'action_set_no_of_recruitment',
'on_click_label': 'employee(s) to recruit',
'force_set': False,
'gauge_value_field': 'no_of_recruitment',
}">
Hired Employees
</field>
</div>
</t>
<t t-if="record.state.raw_value == 'open'">
<div class="oe_start_recruitment">
<p><b>click here</b>, To start the recruitment</p>
<img src="/hr_recruitment/static/src/img/down1.png"/>
</div>
</t>
<div class="oe_launch_recruitment">
<a t-if="record.state.raw_value == 'open'" data-name="job_recruitment" data-type="object" class="oe_kanban_action">Launch Recruitment</a>
<a t-if="record.state.raw_value == 'recruit'" data-name="job_open" data-type="object" class="oe_kanban_action">Recruitment Done</a>
<a t-if="record.survey_id.raw_value"> | </a>
<a t-if="record.survey_id.raw_value" data-name="action_print_survey" data-type="object" class="oe_kanban_action">Print Interview</a>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- hr related job position menu action -->
<record model="ir.actions.act_window" id="action_hr_job">
<field name="name">Job Positions</field>
<field name="res_model">hr.job</field>
<field name="view_type">form</field>
<field name="view_mode">kanban,tree,form</field>
<field name="context">{'search_default_in_recruitment': 1}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click here to create a new job or remove the filter on "In Recruitment" to recruit for an on hold job.
</p>
<p>
Define job position profile and manage recruitment in a context of a particular job: print interview survey, define number of expected new employees, and manage its recruitment pipe
</p>
</field>
</record>
<!-- Stage Tree View -->
<record model="ir.ui.view" id="hr_recruitment_stage_tree">
<field name="name">hr.recruitment.stage.tree</field>

View File

@ -2,7 +2,7 @@
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
# Copyright (C) 2004-Today OpenERP S.A. (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -19,9 +19,11 @@
#
##############################################################################
from openerp import SUPERUSER_ID
from openerp.osv import fields, osv
class hr_applicant_settings(osv.osv_memory):
class hr_applicant_settings(osv.TransientModel):
_name = 'hr.config.settings'
_inherit = ['hr.config.settings', 'fetchmail.config.settings']
@ -32,6 +34,44 @@ class hr_applicant_settings(osv.osv_memory):
'fetchmail_applicants': fields.boolean('Create applicants from an incoming email account',
fetchmail_model='hr.applicant', fetchmail_name='Incoming HR Applications',
help='Allow applicants to send their job application to an email address (jobs@mycompany.com), '
'and create automatically application documents in the system.'),
'and create automatically application documents in the system.',
deprecated='Will be removed with OpenERP v8, not applicable anymore. Use aliases instead.'),
'alias_prefix': fields.char('Default Alias Name for Jobs'),
'alias_domain': fields.char('Alias Domain'),
}
_defaults = {
'alias_domain': lambda self, cr, uid, context: self.pool['mail.alias']._get_alias_domain(cr, SUPERUSER_ID, [1], None, None)[1],
}
def _find_default_job_alias_id(self, cr, uid, context=None):
alias_id = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'hr_recruitment.mail_alias_jobs')
if not alias_id:
alias_ids = self.pool['mail.alias'].search(
cr, uid, [
('alias_model_id.model', '=', 'hr.applicant'),
('alias_force_thread_id', '=', False),
('alias_parent_model_id.model', '=', 'hr.job'),
('alias_parent_thread_id', '=', False),
('alias_defaults', '=', '{}')
], context=context)
alias_id = alias_ids and alias_ids[0] or False
return alias_id
def get_default_alias_prefix(self, cr, uid, ids, context=None):
alias_name = False
alias_id = self._find_default_job_alias_id(cr, uid, context=context)
if alias_id:
alias_name = self.pool['mail.alias'].browse(cr, uid, alias_id, context=context).alias_name
return {'alias_prefix': alias_name}
def set_default_alias_prefix(self, cr, uid, ids, context=None):
mail_alias = self.pool.get('mail.alias')
for record in self.browse(cr, uid, ids, context=context):
alias_id = self._find_default_job_alias_id(cr, uid, context=context)
if not alias_id:
create_ctx = dict(context, alias_model_name='hr.applicant', alias_parent_model_name='hr.job')
alias_id = self.pool['mail.alias'].create(cr, uid, {'alias_name': record.alias_prefix}, context=create_ctx)
else:
mail_alias.write(cr, uid, alias_id, {'alias_name': record.alias_prefix}, context=context)
return True

View File

@ -19,6 +19,14 @@
<label for="module_document"/>
</div>
</div>
<xpath expr="//div[@name='hr_recruitment']" position="after">
<div attrs="{'invisible': ['|',('module_hr_recruitment','=',False),('alias_domain', '=', False)]}">
<label string="Default job email address"/>
<field name="alias_prefix" class="oe_inline" attrs="{'required': [('alias_domain', '!=', False)]}"/>
@
<field name="alias_domain" class="oe_inline" readonly="1"/>
</div>
</xpath>
</field>
</record>
</data>

View File

@ -0,0 +1,2 @@
job_position.css: job_position.sass
sass --trace -t expanded job_position.sass job_position.css

View File

@ -0,0 +1,86 @@
.openerp .oe_kanban_job{
width: 355px;
min-height: 165px !important;
}
.openerp .oe_job_alias{
margin: 3px;
}
.openerp .oe_job_detail{
height: 70px;
width: 308px
}
.openerp .oe_job_alias .oe_e {
font-size: 30px;
line-height: 6px;
vertical-align: top;
margin-right: 3px;
color: white;
text-shadow: 0px 0px 2px black;
float: left;
}
.openerp .oe_job {
font-size: 112%;
position: inline;
margin: 3px 3px;
color: #4c4c4c;
height: 16px;
}
.openerp img.oe_job_avatar {
position: absolute;
width: 24px;
height: 24px;
margin-left: 295px;
margin-top: -5px;
}
.openerp .oe_launch_recruitment{
float: left;
position: absolute;
bottom: 3px;
left: 10px;
}
.openerp div.oe_applications {
position: absolute;;
margin-top: 16px;
font-size: 14px;
}
.openerp .oe_applications > a > span:hover{
margin: 4px 0;
text-decoration: underline;
}
.openerp .oe_name {
font-size: 14px;
font-weight: bold;
}
.openerp .oe_manager_name {
width: 135px;
font-size: 11px;
color: gray;
}
.openerp .oe_job_justgage {
float: right;
margin-top: -40px;
margin-right: -58px;
width:200px;
height:130px;
}
.openerp .oe_department {
width: 350px;
}
.openerp .oe_start_recruitment {
padding-top: 10px;
}
.openerp .oe_start_recruitment p {
font-size: 14px;
color: gray;
padding-left: 50px;
}
.openerp .oe_start_recruitment img {
margin-top: -22px;
width: 32px;
height: 34px;
float: left;
padding-left: 12px;
}
.openerp .oe_job_messages{
margin-top: 40px !important;
}

View File

@ -0,0 +1,70 @@
.openerp
.oe_kanban_job
width: 355px
min-height: 165px !important
.oe_job_alias
margin: 3px
.oe_job_detail
height: 70px
width: 308px
.oe_job_alias .oe_e
font-size: 30px
line-height: 6px
vertical-align: top
margin-right: 3px
color: white
text-shadow: 0px 0px 2px black
float: left
.oe_job
font-size: 112%
position: inline
margin: 3px 3px
color: #4c4c4c
height: 16px
img.oe_job_avatar
position: absolute
width: 24px
height: 24px
margin-left: 295px
margin-top: -5px
.oe_launch_recruitment
float: left
position: absolute
bottom: 3px
left: 10px
div.oe_applications
position: absolute
margin-top: 16px
font-size: 14px
.oe_applications > a > span:hover
margin: 4px 0
text-decoration: underline
.oe_name
font-size: 14px
font-weight: bold
.oe_manager_name
width: 135px
font-size: 11px
color: gray
.oe_job_justgage
float: right
margin-top: -40px
margin-right: -58px
width: 200px
height: 130px
.oe_department
width: 350px
.oe_start_recruitment
padding-top: 10px
p
font-size: 14px
color: gray
padding-left: 50px
img
margin-top: -22px
width: 32px
height: 34px
float: left
padding-left: 12px
.oe_job_messages
margin-top: 40px !important

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -0,0 +1,13 @@
openerp.hr_recruitment = function (openerp) {
openerp.web_kanban.KanbanRecord.include({
on_card_clicked: function() {
if (this.view.dataset.model === 'hr.job') {
this.$('.oe_applications a').first().click();
} else {
this._super.apply(this, arguments);
}
},
});
}

View File

@ -95,7 +95,6 @@ class EscposDriver(Thread):
_logger.warning('ESC/POS Device Disconnected: '+message)
def run(self):
self.queue = Queue()
while True:
try:
timestamp, task, data = self.queue.get(True)

View File

@ -129,10 +129,6 @@ class Scanner(Thread):
self.set_status('error',str(e))
return None
@http.route('/hw_proxy/Vis_scanner_connected', type='json', auth='none', cors='*')
def is_scanner_connected(self):
return self.get_device() != None
def get_barcode(self):
""" Returns a scanned barcode. Will wait at most 5 seconds to get a barcode, and will
return barcode scanned in the past if they are not older than 5 seconds and have not

View File

@ -154,6 +154,12 @@ class mail_alias(osv.Model):
sequence = (sequence + 1) if sequence else 2
return new_name
def _clean_and_make_unique(self, cr, uid, name, context=None):
# when an alias name appears to already be an email, we keep the local part only
name = remove_accents(name).lower().split('@')[0]
name = re.sub(r'[^\w+.]+', '-', name)
return self._find_unique(cr, uid, name, context=context)
def migrate_to_alias(self, cr, child_model_name, child_table_name, child_model_auto_init_fct,
alias_model_name, alias_id_column, alias_key, alias_prefix='', alias_force_key='', alias_defaults={},
alias_generate_name=False, context=None):
@ -199,7 +205,7 @@ class mail_alias(osv.Model):
alias_vals['alias_parent_thread_id'] = obj_data['id']
alias_create_ctx = dict(context, alias_model_name=alias_model_name, alias_parent_model_name=child_model_name)
alias_id = mail_alias.create(cr, SUPERUSER_ID, alias_vals, context=alias_create_ctx)
child_class_model.write(cr, SUPERUSER_ID, obj_data['id'], {'alias_id': alias_id})
child_class_model.write(cr, SUPERUSER_ID, obj_data['id'], {'alias_id': alias_id}, context={'mail_notrack': True})
_logger.info('Mail alias created for %s %s (id %s)', child_model_name, obj_data[alias_key], obj_data['id'])
# Finally attempt to reinstate the missing constraint
@ -227,11 +233,7 @@ class mail_alias(osv.Model):
model_name = context.get('alias_model_name')
parent_model_name = context.get('alias_parent_model_name')
if vals.get('alias_name'):
# when an alias name appears to already be an email, we keep the local part only
alias_name = remove_accents(vals['alias_name']).lower().split('@')[0]
alias_name = re.sub(r'[^\w+.]+', '-', alias_name)
alias_name = self._find_unique(cr, uid, alias_name, context=context)
vals['alias_name'] = alias_name
vals['alias_name'] = self._clean_and_make_unique(cr, uid, vals.get('alias_name'), context=context)
if model_name:
model_id = self.pool.get('ir.model').search(cr, uid, [('model', '=', model_name)], context=context)[0]
vals['alias_model_id'] = model_id
@ -240,6 +242,12 @@ class mail_alias(osv.Model):
vals['alias_parent_model_id'] = model_id
return super(mail_alias, self).create(cr, uid, vals, context=context)
def write(self, cr, uid, ids, vals, context=None):
""""give uniqe alias name if given alias name is allready assigned"""
if vals.get('alias_name'):
vals['alias_name'] = self._clean_and_make_unique(cr, uid, vals.get('alias_name'), context=context)
return super(mail_alias, self).write(cr, uid, ids, vals, context=context)
def open_document(self, cr, uid, ids, context=None):
alias = self.browse(cr, uid, ids, context=context)[0]
if not alias.alias_model_id or not alias.alias_force_thread_id:

View File

@ -113,9 +113,9 @@ class mail_thread(osv.AbstractModel):
object_id.alias_id.alias_model_id.model == self._name and \
object_id.alias_id.alias_force_thread_id == 0:
alias = object_id.alias_id
elif catchall_domain and model: # no specific res_id given -> generic help message, take an example alias (i.e. alias of some section_id)
if not alias and catchall_domain and model: # no res_id or res_id not linked to an alias -> generic help message, take a generic alias of the model
alias_obj = self.pool.get('mail.alias')
alias_ids = alias_obj.search(cr, uid, [("alias_parent_model_id.model", "=", model), ("alias_name", "!=", False), ('alias_force_thread_id', '=', False)], context=context, order='id ASC')
alias_ids = alias_obj.search(cr, uid, [("alias_parent_model_id.model", "=", model), ("alias_name", "!=", False), ('alias_force_thread_id', '=', False), ('alias_parent_thread_id', '=', False)], context=context, order='id ASC')
if alias_ids and len(alias_ids) == 1:
alias = alias_obj.browse(cr, uid, alias_ids[0], context=context)
@ -384,7 +384,10 @@ class mail_thread(osv.AbstractModel):
track_ctx = dict(context)
if 'lang' not in track_ctx:
track_ctx['lang'] = self.pool.get('res.users').browse(cr, uid, uid, context=context).lang
tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=track_ctx)
if not context.get('mail_notrack'):
tracked_fields = self._get_tracked_fields(cr, uid, values.keys(), context=track_ctx)
else:
tracked_fields = []
if tracked_fields:
records = self.browse(cr, uid, ids, context=track_ctx)
initial_values = dict((this.id, dict((key, getattr(this, key)) for key in tracked_fields.keys())) for this in records)

2362
addons/mrp/i18n/th.po Normal file

File diff suppressed because it is too large Load Diff

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
"PO-Revision-Date: 2011-12-06 10:25+0000\n"
"Last-Translator: qdp (OpenERP) <qdp-launchpad@tinyerp.com>\n"
"PO-Revision-Date: 2014-02-12 04:36+0000\n"
"Last-Translator: Bluce <igamall@yahoo.com.tw>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-01-28 05:39+0000\n"
"X-Generator: Launchpad (build 16914)\n"
"X-Launchpad-Export-Date: 2014-02-13 05:48+0000\n"
"X-Generator: Launchpad (build 16916)\n"
#. module: mrp
#: help:mrp.config.settings,module_mrp_repair:0
@ -47,7 +47,7 @@ msgstr "工作中心使用率"
#. module: mrp
#: view:mrp.routing.workcenter:0
msgid "Routing Work Centers"
msgstr "程工作中心"
msgstr "程工作中心"
#. module: mrp
#: field:mrp.production.workcenter.line,cycle:0
@ -83,12 +83,12 @@ msgstr "MRP 工作中心"
#: model:ir.actions.act_window,name:mrp.mrp_routing_action
#: model:ir.ui.menu,name:mrp.menu_mrp_routing_action
msgid "Routings"
msgstr "程"
msgstr "程"
#. module: mrp
#: view:mrp.bom:0
msgid "Search Bill Of Material"
msgstr "搜尋料清單"
msgstr "搜尋料清單"
#. module: mrp
#: model:process.node,note:mrp.process_node_stockproduct1
@ -244,6 +244,13 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
"點選建立一個製程(Routing)。\n"
"</p><p>\n"
"製程(Routing)允許您建立並管理在工作中心內應被遵循的製造程序(即工作順序),用以生產產品。\n"
"它們被附加於用以定義所需原物料的物料清單(BOM)。\n"
"</p>\n"
" "
#. module: mrp
#: view:mrp.production:0
@ -299,7 +306,7 @@ msgstr "預定之貨品"
#. module: mrp
#: selection:mrp.bom,type:0
msgid "Sets / Phantom"
msgstr "套件 / 虛項"
msgstr "組 / 虛擬物料"
#. module: mrp
#: view:mrp.production:0
@ -315,7 +322,7 @@ msgstr "於外部計劃中之位置的引用。"
#. module: mrp
#: model:res.groups,name:mrp.group_mrp_routings
msgid "Manage Routings"
msgstr "管理程"
msgstr "管理程"
#. module: mrp
#: model:ir.model,name:mrp.model_mrp_product_produce
@ -393,7 +400,7 @@ msgstr "確認生產"
msgid ""
"The system creates an order (production or purchased) depending on the sold "
"quantity and the products parameters."
msgstr ""
msgstr "系統依據已售數量與產品參數建立一個供給訂單(製造單或採購單)。"
#. module: mrp
#: model:process.transition,note:mrp.process_transition_servicemts0
@ -461,6 +468,15 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" 點選建立一個新的屬性。\n"
" </p><p>\n"
" OpenERP的屬性(properties)是使用於選擇正確的物料表(BoM)於製造產品,\n"
"    當您有不同的方法來建立同一產品。您能指定數個屬性給每一個物料表。\n"
"    當一位業務人員建立一張銷售訂單時,他們可以將銷售訂單關聯至數個屬性,\n"
"    並且OpenERP將根據需求自動選擇物料表(BoM)。\n"
" </p>\n"
" "
#. module: mrp
#: view:mrp.production:0
@ -539,7 +555,7 @@ msgstr ""
msgid ""
"The Bill of Material is linked to a routing, i.e. the succession of work "
"centers."
msgstr ""
msgstr "物料清單(BOM)已連結至一個製程(工作順序),如各工作中心的接續"
#. module: mrp
#: view:mrp.production:0
@ -566,7 +582,7 @@ msgstr "強制預留"
#. module: mrp
#: field:report.mrp.inout,value:0
msgid "Stock value"
msgstr ""
msgstr "庫存評價"
#. module: mrp
#: model:ir.actions.act_window,name:mrp.action_product_bom_structure
@ -773,7 +789,7 @@ msgstr "每月"
msgid ""
"Unit of Measure (Unit of Measure) is the unit of measurement for the "
"inventory control"
msgstr ""
msgstr "度量單位(UoM)是庫存管理的計算單位。"
#. module: mrp
#: report:bom.structure:0
@ -824,7 +840,7 @@ msgstr "標示為開始"
#. module: mrp
#: view:mrp.production:0
msgid "Partial"
msgstr "部份"
msgstr "分批"
#. module: mrp
#: report:mrp.production.order:0

View File

@ -99,8 +99,6 @@ class mrp_workcenter(osv.osv):
value = {'costs_hour': cost.standard_price}
return {'value': value}
class mrp_routing(osv.osv):
"""
For specifying the routings of Work Centers.
@ -288,16 +286,20 @@ class mrp_bom(osv.osv):
(_check_product, 'BoM line product should not be same as BoM product.', ['product_id']),
]
def onchange_product_id(self, cr, uid, ids, product_id, name, context=None):
def onchange_product_id(self, cr, uid, ids, product_id, name, product_qty=0, context=None):
""" Changes UoM and name if product_id changes.
@param name: Name of the field
@param product_id: Changed product_id
@return: Dictionary of changed values
"""
res = {}
if product_id:
prod = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
return {'value': {'name': prod.name, 'product_uom': prod.uom_id.id}}
return {}
res['value'] = {'name': prod.name, 'product_uom': prod.uom_id.id, 'product_uos_qty': 0, 'product_uos': False}
if prod.uos_id.id:
res['value']['product_uos_qty'] = product_qty * prod.uos_coeff
res['value']['product_uos'] = prod.uos_id.id
return res
def onchange_uom(self, cr, uid, ids, product_id, product_uom, context=None):
res = {'value': {}}
@ -597,16 +599,19 @@ class mrp_production(osv.osv):
return {'value': {'location_dest_id': src}}
return {}
def product_id_change(self, cr, uid, ids, product_id, context=None):
def product_id_change(self, cr, uid, ids, product_id, product_qty=0, context=None):
""" Finds UoM of changed product.
@param product_id: Id of changed product.
@return: Dictionary of values.
"""
result = {}
if not product_id:
return {'value': {
'product_uom': False,
'bom_id': False,
'routing_id': False
'routing_id': False,
'product_uos_qty': 0,
'product_uos': False
}}
bom_obj = self.pool.get('mrp.bom')
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
@ -615,14 +620,13 @@ class mrp_production(osv.osv):
if bom_id:
bom_point = bom_obj.browse(cr, uid, bom_id, context=context)
routing_id = bom_point.routing_id.id or False
product_uom_id = product.uom_id and product.uom_id.id or False
result = {
'product_uom': product_uom_id,
'bom_id': bom_id,
'routing_id': routing_id,
}
return {'value': result}
product_uos_id = product.uos_id and product.uos_id.id or False
result['value'] = {'product_uos_qty': 0, 'product_uos': False, 'product_uom': product_uom_id, 'bom_id': bom_id, 'routing_id': routing_id}
if product.uos_id.id:
result['value']['product_uos_qty'] = product_qty * product.uos_coeff
result['value']['product_uos'] = product.uos_id.id
return result
def bom_id_change(self, cr, uid, ids, bom_id, context=None):
""" Finds routing for changed BoM.
@ -1031,11 +1035,12 @@ class mrp_production(osv.osv):
'location_dest_id': destination_location_id,
'move_dest_id': production.move_prod_id.id,
'company_id': production.company_id.id,
'production_id': production.id,
}
move_id = stock_move.create(cr, uid, data, context=context)
stock_move.action_confirm(cr, uid, [move_id], context=context)
production.write({'move_created_ids': [(6, 0, [move_id])]}, context=context)
return move_id
#a phantom bom cannot be used in mrp order so it's ok to assume the list returned by action_confirm
#is 1 element long, so we can take the first.
return stock_move.action_confirm(cr, uid, [move_id], context=context)[0]
def _make_production_consume_line(self, cr, uid, production_line, parent_move_id, source_location_id=False, context=None):
stock_move = self.pool.get('stock.move')
@ -1058,10 +1063,10 @@ class mrp_production(osv.osv):
'location_dest_id': destination_location_id,
'company_id': production.company_id.id,
'procure_method': 'make_to_order',
'raw_material_production_id': production.id,
})
stock_move.action_confirm(cr, uid, [move_id], context=context)
production.write({'move_lines': [(4, move_id)]}, context=context)
return move_id
return True
def action_confirm(self, cr, uid, ids, context=None):
""" Confirms production order.

View File

@ -347,10 +347,10 @@
<form string="Bill of Material" version="7.0">
<group>
<group>
<field name="product_id" on_change="onchange_product_id(product_id, name, context)" class="oe_inline"/>
<field name="product_id" on_change="onchange_product_id(product_id, name, product_qty, context)" context="{'default_supply_method':'produce'}" class="oe_inline"/>
<label for="product_qty" string="Quantity"/>
<div>
<field name="product_qty" class="oe_inline"/>
<field name="product_qty" class="oe_inline" on_change="onchange_product_id(product_id, name, product_qty, context)"/>
<field name="product_uom" class="oe_inline" on_change="onchange_uom(product_id, product_uom)" groups="product.group_uom"/>
</div>
<label for="product_uos_qty" groups="product.group_uos"/>
@ -366,7 +366,7 @@
</div>
</group>
<group>
<field name="name" groups="base.group_no_one"/>
<field name="name" groups="product.group_mrp_properties"/>
<field name="code" string="Reference"/>
<field name="type"/>
<p colspan="2" class="oe_grey" attrs="{'invisible': [('type','=','normal')]}">
@ -687,15 +687,14 @@
</div>
<group>
<group>
<field name="product_id" on_change="product_id_change(product_id)" domain="[('bom_ids','!=',False),('bom_ids.bom_id','=',False)]" class="oe_inline" context='{"default_type": "product"}'/>
<field name="product_id" on_change="product_id_change(product_id, product_qty)" domain="[('bom_ids','!=',False),('bom_ids.bom_id','=',False),('bom_ids.type','!=','phantom')]" class="oe_inline" context='{"default_type": "product"}'/>
<label for="product_qty"/>
<div>
<field name="product_qty" class="oe_inline"/>
<field name="product_qty" class="oe_inline" on_change="product_id_change(product_id, product_qty)"/>
<field name="product_uom" groups="product.group_uom" class="oe_inline"/>
<button type="action"
icon="terp-accessories-archiver+"
name="%(mrp.action_change_production_qty)d"
string="(Update)" states="confirmed" class="oe_edit_only oe_link"/>
string="Update" states="confirmed" class="oe_edit_only oe_link"/>
</div>
<label for="product_uos_qty" groups="product.group_uos"/>
<div groups="product.group_uos">

View File

@ -22,7 +22,9 @@
from openerp.osv import fields
from openerp.osv import osv
from openerp.tools.translate import _
from openerp import SUPERUSER_ID
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
import time
class StockMove(osv.osv):
_inherit = 'stock.move'
@ -33,6 +35,12 @@ class StockMove(osv.osv):
'consumed_for': fields.many2one('stock.move', 'Consumed for', help='Technical field used to make the traceability of produced products'),
}
def copy(self, cr, uid, id, default=None, context=None):
if not default:
default = {}
default['production_id'] = False
return super(StockMove, self).copy(cr, uid, id, default, context=context)
def check_tracking(self, cr, uid, move, lot_id, context=None):
super(StockMove, self).check_tracking(cr, uid, move, lot_id, context=context)
if move.product_id.track_production and (move.location_id.usage == 'production' or move.location_dest_id.usage == 'production') and not lot_id:
@ -40,6 +48,20 @@ class StockMove(osv.osv):
if move.raw_material_production_id and move.location_dest_id.usage == 'production' and move.raw_material_production_id.product_id.track_production and not move.consumed_for:
raise osv.except_osv(_('Warning!'), _("Because the product %s requires it, you must assign a serial number to your raw material %s to proceed further in your production. Please use the 'Produce' button to do so.") % (move.raw_material_production_id.product_id.name, move.product_id.name))
def _check_phantom_bom(self, cr, uid, move, context=None):
"""check if product associated to move has a phantom bom
return list of ids of mrp.bom for that product """
user_company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
#doing the search as SUPERUSER because a user with the permission to write on a stock move should be able to explode it
#without giving him the right to read the boms.
return self.pool.get('mrp.bom').search(cr, SUPERUSER_ID, [
('product_id', '=', move.product_id.id),
('bom_id', '=', False),
('type', '=', 'phantom'),
'|', ('date_start', '=', False), ('date_start', '<=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
'|', ('date_stop', '=', False), ('date_stop', '>=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
('company_id', '=', user_company)], context=context)
def _action_explode(self, cr, uid, move, context=None):
""" Explodes pickings.
@param move: Stock moves
@ -49,59 +71,52 @@ class StockMove(osv.osv):
move_obj = self.pool.get('stock.move')
procurement_obj = self.pool.get('procurement.order')
product_obj = self.pool.get('product.product')
processed_ids = [move.id]
bis = bom_obj.search(cr, uid, [
('product_id', '=', move.product_id.id),
('bom_id', '=', False),
('type', '=', 'phantom')])
to_explode_again_ids = []
processed_ids = []
bis = self._check_phantom_bom(cr, uid, move, context=context)
if bis:
factor = move.product_qty
bom_point = bom_obj.browse(cr, uid, bis[0], context=context)
res = bom_obj._bom_explode(cr, uid, bom_point, factor, [])
bom_point = bom_obj.browse(cr, SUPERUSER_ID, bis[0], context=context)
res = bom_obj._bom_explode(cr, SUPERUSER_ID, bom_point, factor, [])
state = 'confirmed'
if move.state == 'assigned':
state = 'assigned'
for line in res[0]:
valdef = {
'picking_id': move.picking_id.id,
'picking_id': move.picking_id.id if move.picking_id else False,
'product_id': line['product_id'],
'product_uom': line['product_uom'],
'product_qty': line['product_qty'],
'product_uos': line['product_uos'],
'product_uos_qty': line['product_uos_qty'],
'move_dest_id': move.id,
'state': state,
'name': line['name'],
'procurements': [],
}
mid = move_obj.copy(cr, uid, move.id, default=valdef)
processed_ids.append(mid)
prodobj = product_obj.browse(cr, uid, line['product_id'], context=context)
proc_id = procurement_obj.create(cr, uid, {
'name': (move.picking_id.origin or ''),
'origin': (move.picking_id.origin or ''),
'date_planned': move.date,
'product_id': line['product_id'],
'product_qty': line['product_qty'],
'product_uom': line['product_uom'],
'product_uos_qty': line['product_uos'] and line['product_uos_qty'] or False,
'product_uos': line['product_uos'],
'location_id': move.location_id.id,
'procure_method': prodobj.procure_method,
'move_id': mid,
})
procurement_obj.signal_button_confirm(cr, uid, [proc_id])
to_explode_again_ids.append(mid)
move_obj.write(cr, uid, [move.id], {
'location_dest_id': move.location_id.id, # dummy move for the kit
'picking_id': False,
'state': 'confirmed'
})
procurement_ids = procurement_obj.search(cr, uid, [('move_id', '=', move.id)], context)
procurement_obj.signal_button_confirm(cr, uid, procurement_ids)
procurement_obj.signal_button_wait_done(cr, uid, procurement_ids)
return processed_ids
#delete the move with original product which is not relevant anymore
move_obj.unlink(cr, SUPERUSER_ID, [move.id], context=context)
#check if new moves needs to be exploded
if to_explode_again_ids:
for new_move in self.browse(cr, uid, to_explode_again_ids, context=context):
processed_ids.extend(self._action_explode(cr, uid, new_move, context=context))
#return list of newly created move or the move id otherwise
return processed_ids or [move.id]
def action_confirm(self, cr, uid, ids, context=None):
move_ids = []
for move in self.browse(cr, uid, ids, context=context):
#in order to explode a move, we must have a picking_type_id on that move because otherwise the move
#won't be assigned to a picking and it would be weird to explode a move into several if they aren't
#all grouped in the same picking.
if move.picking_type_id:
move_ids.extend(self._action_explode(cr, uid, move, context=context))
else:
move_ids.append(move.id)
#we go further with the list of ids potentially changed by action_explode
return super(StockMove, self).action_confirm(cr, uid, move_ids, context=context)
def action_consume(self, cr, uid, ids, product_qty, location_id=False, restrict_lot_id=False, restrict_partner_id=False,
consumed_for=False, context=None):
@ -121,9 +136,15 @@ class StockMove(osv.osv):
if product_qty <= 0:
raise osv.except_osv(_('Warning!'), _('Please provide proper quantity.'))
#because of the action_confirm that can create extra moves in case of phantom bom, we need to make 2 loops
ids2 = []
for move in self.browse(cr, uid, ids, context=context):
if move.state == 'draft':
self.action_confirm(cr, uid, [move.id], context=context)
ids2.extend(self.action_confirm(cr, uid, [move.id], context=context))
else:
ids2.append(move.id)
for move in self.browse(cr, uid, ids2, context=context):
move_qty = move.product_qty
uom_qty = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, product_qty, move.product_uom.id)
if move_qty <= 0:
@ -182,22 +203,6 @@ class StockMove(osv.osv):
workflow.trg_trigger(uid, 'stock.move', move.id, cr)
return res
class StockPicking(osv.osv):
_inherit = 'stock.picking'
#
# Explode picking by replacing phantom BoMs
#
def action_explode(self, cr, uid, move_ids, *args):
"""Explodes moves by expanding kit components"""
move_obj = self.pool.get('stock.move')
todo = move_ids[:]
for move in move_obj.browse(cr, uid, move_ids):
todo.extend(move_obj._action_explode(cr, uid, move))
return list(set(todo))
class stock_warehouse(osv.osv):
_inherit = 'stock.warehouse'
_columns = {
@ -259,11 +264,12 @@ class stock_warehouse(osv.osv):
all_routes += [warehouse.manufacture_pull_id.route_id.id]
return all_routes
def _handle_renaming(self, cr, uid, warehouse, name, context=None):
res = super(stock_warehouse, self)._handle_renaming(cr, uid, warehouse, name, context=context)
def _handle_renaming(self, cr, uid, warehouse, name, code, context=None):
res = super(stock_warehouse, self)._handle_renaming(cr, uid, warehouse, name, code, context=context)
pull_obj = self.pool.get('procurement.rule')
#change the manufacture pull rule name
pull_obj.write(cr, uid, warehouse.manufacture_pull_id.id, {'name': warehouse.manufacture_pull_id.name.replace(warehouse.name, name, 1)}, context=context)
if warehouse.manufacture_pull_id:
pull_obj.write(cr, uid, warehouse.manufacture_pull_id.id, {'name': warehouse.manufacture_pull_id.name.replace(warehouse.name, name, 1)}, context=context)
return res
def _get_all_products_to_resupply(self, cr, uid, warehouse, context=None):

View File

@ -544,11 +544,11 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
this.order.removeOrderline(this);
return;
}else{
var quant = Math.max(parseFloat(quantity) || 0, 0);
var quant = parseFloat(quantity) || 0;
var unit = this.get_unit();
if(unit){
this.quantity = Math.max(unit.rounding, round_pr(quant, unit.rounding));
this.quantityStr = this.quantity.toFixed(Math.max(0,Math.ceil(Math.log(1.0 / unit.rounding) / Math.log(10))));
this.quantity = round_pr(quant, unit.rounding);
this.quantityStr = this.quantity.toFixed(Math.ceil(Math.log(1.0 / unit.rounding) / Math.log(10)));
}else{
this.quantity = quant;
this.quantityStr = '' + this.quantity;
@ -1104,10 +1104,11 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
}
},
switchSign: function() {
console.log('switchsing');
var oldBuffer;
oldBuffer = this.get('buffer');
this.set({
buffer: oldBuffer[0] === '-' ? oldBuffer.substr(1) : "-" + oldBuffer
buffer: oldBuffer[0] === '-' ? oldBuffer.substr(1) : "-" + oldBuffer
});
this.trigger('set_value',this.get('buffer'));
},

View File

@ -1125,8 +1125,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
},
is_paid: function(){
var currentOrder = this.pos.get('selectedOrder');
return (currentOrder.getTotalTaxIncluded() >= 0.000001
&& currentOrder.getPaidTotal() + 0.000001 >= currentOrder.getTotalTaxIncluded());
return (currentOrder.getTotalTaxIncluded() < 0.000001
|| currentOrder.getPaidTotal() + 0.000001 >= currentOrder.getTotalTaxIncluded());
},
validate_order: function(options) {

View File

@ -250,6 +250,7 @@ class procurement_order(osv.osv):
def _run(self, cr, uid, procurement, context=None):
'''This method implements the resolution of the given procurement
:param procurement: browse record
:returns: True if the resolution of the procurement was a success, False otherwise to set it in exception
'''
return True

View File

@ -130,7 +130,7 @@ class product_uom(osv.osv):
_order = "name"
_columns = {
'name': fields.char('Unit of Measure', required=True, translate=True),
'category_id': fields.many2one('product.uom.categ', 'Category', required=True, ondelete='cascade',
'category_id': fields.many2one('product.uom.categ', 'Product Category', required=True, ondelete='cascade',
help="Conversion between Units of Measure can only occur if they belong to the same category. The conversion will be made based on the ratios."),
'factor': fields.float('Ratio', required=True,digits=(12, 12),
help='How much bigger or smaller this unit is compared to the reference Unit of Measure for this category:\n'\
@ -578,7 +578,7 @@ class product_product(osv.osv):
if context is None:
context = {}
product = self.browse(cr, uid, product_id, context=context)
date = context.get('history_date', time.strftime('%Y-%m-%d %H:%M:%s'))
date = context.get('history_date', time.strftime('%Y-%m-%d %H:%M:%S'))
prices_history_obj = self.pool.get('prices.history')
history_ids = prices_history_obj.search(cr, uid, [('company_id', '=', company_id), ('product_template_id', '=', product.product_tmpl_id.id), ('datetime', '<=', date)], limit=1)
if history_ids:

View File

@ -20,7 +20,7 @@
<!-- Resource: project.project -->
<record id="all_projects_account" model="account.analytic.account">
<field name="name">Projects</field>
<field name="code">3</field>
<field name="code">PP001</field>
<field name="type">view</field>
</record>
<function id="parent_project_default_set" model="ir.values" name="set" eval="('default',False,'parent_id', [('project.project', False)], all_projects_account, True, False, False, False, True)"/>

View File

@ -19,7 +19,6 @@
#
##############################################################################
import time
import pytz
from openerp import SUPERUSER_ID, workflow
from datetime import datetime
@ -154,11 +153,11 @@ class purchase_order(osv.osv):
obj_data = self.pool.get('ir.model.data')
return obj_data.get_object_reference(cr, uid, 'stock','picking_type_in') and obj_data.get_object_reference(cr, uid, 'stock','picking_type_in')[1] or False
def _get_picking_ids(self, cr, uid, ids, name, args, context=None):
def _get_picking_ids(self, cr, uid, ids, field_names, args, context=None):
res = {}
for purchase_id in ids:
picking_ids = set()
move_ids = self.pool.get('stock.move').search(cr, uid, [('purchase_line_id.order_id','=', purchase_id)] , context=context)
move_ids = self.pool.get('stock.move').search(cr, uid, [('purchase_line_id.order_id', '=', purchase_id)], context=context)
for move in self.pool.get('stock.move').browse(cr, uid, move_ids, context=context):
picking_ids.add(move.picking_id.id)
res[purchase_id] = list(picking_ids)
@ -206,7 +205,7 @@ class purchase_order(osv.osv):
'validator' : fields.many2one('res.users', 'Validated by', readonly=True),
'notes': fields.text('Terms and Conditions'),
'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', help="Invoices generated for a purchase order"),
'picking_ids': fields.function(_get_picking_ids, method=True, type='one2many', relation='stock.picking', string='Picking List', help="This is the list of operations that have been generated for this purchase order."),
'picking_ids': fields.function(_get_picking_ids, method=True, type='one2many', relation='stock.picking', string='Picking List', help="This is the list of reception operations that have been generated for this purchase order."),
'shipped':fields.boolean('Received', readonly=True, select=True, help="It indicates that a picking has been done"),
'shipped_rate': fields.function(_shipped_rate, string='Received Ratio', type='float'),
'invoiced': fields.function(_invoiced, string='Invoice Received', type='boolean', help="It indicates that an invoice has been paid"),
@ -408,6 +407,7 @@ class purchase_order(osv.osv):
action = self.pool.get('ir.actions.act_window').read(cr, uid, action_id, context=context)
pick_ids = []
#TODO: might need to change this function in order to return the whole set of operations and not only the reception(s) to input
for po in self.browse(cr, uid, ids, context=context):
pick_ids += [picking.id for picking in po.picking_ids]
@ -673,11 +673,10 @@ class purchase_order(osv.osv):
if order.currency_id.id != order.company_id.currency_id.id:
#we don't round the price_unit, as we may want to store the standard price with more digits than allowed by the currency
price_unit = self.pool.get('res.currency').compute(cr, uid, order.currency_id.id, order.company_id.currency_id.id, price_unit, round=False, context=context)
res = [{
res = []
move_template = {
'name': order_line.name or '',
'product_id': order_line.product_id.id,
'product_uom_qty': order_line.product_qty,
'product_uos_qty': order_line.product_qty,
'product_uom': order_line.product_uom.id,
'product_uos': order_line.product_uom.id,
'date': self.date_to_datetime(cr, uid, order.date_order, context),
@ -686,34 +685,37 @@ class purchase_order(osv.osv):
'location_dest_id': order.location_id.id,
'picking_id': picking_id,
'partner_id': order.dest_address_id.id or order.partner_id.id,
'move_dest_id': order_line.move_dest_id.id,
'move_dest_id': False,
'state': 'draft',
'purchase_line_id': order_line.id,
'company_id': order.company_id.id,
'price_unit': price_unit,
'picking_type_id': order.picking_type_id.id,
'group_id': group_id,
'procurement_id': order_line.procurement_ids and order_line.procurement_ids[0].id or False,
'procurement_id': False,
'origin': order.name,
'route_ids': order.picking_type_id.warehouse_id and [(6, 0, [x.id for x in order.picking_type_id.warehouse_id.route_ids])] or [],
}]
}
diff_quantity = order_line.product_qty
for procurement in order_line.procurement_ids:
procurement_qty = product_uom._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, to_uom_id=order_line.product_uom.id)
tmp = move_template.copy()
tmp.update({
'product_uom_qty': min(procurement_qty, diff_quantity),
'product_uos_qty': min(procurement_qty, diff_quantity),
'move_dest_id': procurement.move_dest_id.id, # blabla
'group_id': procurement.group_id.id or group_id, # blabla to check ca devrait etre bon et groupé dans le meme picking qd meme
'procurement_id': procurement.id,
})
diff_quantity -= min(procurement_qty, diff_quantity)
res.append(tmp)
#if the order line has a bigger quantity than the procurement it was for (manually changed or minimal quantity), then
#split the future stock move in two because the route followed may be different.
if order_line.procurement_ids:
procurement = order_line.procurement_ids[0]
procurement_quantity = product_uom._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, to_uom_id=order_line.product_uom.id)
diff_quantity = order_line.product_qty - procurement_quantity
if diff_quantity > 0:
tmp = res[0].copy()
res[0]['product_uom_qty'] = diff_quantity
res[0]['product_uos_qty'] = diff_quantity
res[0]['procurement_id'] = False
res[0]['move_dest_id'] = False
tmp['product_uom_qty'] = procurement.product_qty
tmp['product_uos_qty'] = procurement.product_qty
tmp['product_uom'] = procurement.product_uom.id
tmp['product_uos'] = procurement.product_uom.id
res.append(tmp)
if diff_quantity > 0:
move_template['product_uom_qty'] = diff_quantity
move_template['product_uos_qty'] = diff_quantity
res.append(move_template)
return res
def _create_stock_moves(self, cr, uid, order, order_lines, picking_id=False, context=None):
@ -737,21 +739,18 @@ class purchase_order(osv.osv):
"""
stock_move = self.pool.get('stock.move')
todo_moves = []
new_group = False
if any([(not x.group_id) for x in order_lines]):
new_group = self.pool.get("procurement.group").create(cr, uid, {'name': order.name, 'partner_id': order.partner_id.id}, context=context)
new_group = self.pool.get("procurement.group").create(cr, uid, {'name': order.name, 'partner_id': order.partner_id.id}, context=context)
for order_line in order_lines:
if not order_line.product_id:
continue
if order_line.product_id.type in ('product', 'consu'):
group_id = order_line.group_id and order_line.group_id.id or new_group
for vals in self._prepare_order_line_move(cr, uid, order, order_line, picking_id, group_id, context=context):
for vals in self._prepare_order_line_move(cr, uid, order, order_line, picking_id, new_group, context=context):
move = stock_move.create(cr, uid, vals, context=context)
todo_moves.append(move)
stock_move.action_confirm(cr, uid, todo_moves)
todo_moves = stock_move.action_confirm(cr, uid, todo_moves)
stock_move.force_assign(cr, uid, todo_moves)
def test_moves_done(self, cr, uid, ids, context=None):
@ -785,7 +784,8 @@ class purchase_order(osv.osv):
def action_picking_create(self, cr, uid, ids, context=None):
for order in self.browse(cr, uid, ids):
self._create_stock_moves(cr, uid, order, order.order_line, None, context=context)
picking_id = self.pool.get('stock.picking').create(cr, uid, {'picking_type_id': order.picking_type_id.id, 'partner_id': order.partner_id.id}, context=context)
self._create_stock_moves(cr, uid, order, order.order_line, picking_id, context=context)
def picking_done(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'shipped':1,'state':'approved'}, context=context)
@ -800,8 +800,7 @@ class purchase_order(osv.osv):
'shipped':False,
'invoiced':False,
'invoice_ids': [],
'picking_ids': [],
'origin' : '',
'origin': '',
'partner_ref': '',
'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
})
@ -832,7 +831,7 @@ class purchase_order(osv.osv):
list_key = []
for field in fields:
field_val = getattr(br, field)
if field in ('product_id', 'move_dest_id', 'account_analytic_id'):
if field in ('product_id', 'account_analytic_id'):
if not field_val:
field_val = False
if isinstance(field_val, browse_record):
@ -939,7 +938,6 @@ class purchase_order_line(osv.osv):
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
'product_id': fields.many2one('product.product', 'Product', domain=[('purchase_ok','=',True)], change_default=True),
'move_ids': fields.one2many('stock.move', 'purchase_line_id', 'Reservation', readonly=True, ondelete='set null'),
'move_dest_id': fields.many2one('stock.move', 'Reservation Destination', ondelete='set null'),
'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Product Price')),
'price_subtotal': fields.function(_amount_line, string='Subtotal', digits_compute= dp.get_precision('Account')),
'order_id': fields.many2one('purchase.order', 'Order Reference', select=True, required=True, ondelete='cascade'),
@ -955,7 +953,6 @@ class purchase_order_line(osv.osv):
'partner_id': fields.related('order_id','partner_id',string='Partner',readonly=True,type="many2one", relation="res.partner", store=True),
'date_order': fields.related('order_id','date_order',string='Order Date',readonly=True,type="date"),
'procurement_ids': fields.one2many('procurement.order', 'purchase_line_id', string='Associated procurements'),
'group_id': fields.related('procurement_ids', 'group_id', type='many2one', relation='procurement.group', string='Procurement Group'),
}
_defaults = {
'product_uom' : _get_uom_id,
@ -970,7 +967,7 @@ class purchase_order_line(osv.osv):
def copy_data(self, cr, uid, id, default=None, context=None):
if not default:
default = {}
default.update({'state':'draft', 'move_ids':[],'invoiced':0,'invoice_lines':[]})
default.update({'state':'draft', 'move_ids':[], 'invoiced':0, 'invoice_lines':[], 'procurement_ids': False})
return super(purchase_order_line, self).copy_data(cr, uid, id, default, context)
def unlink(self, cr, uid, ids, context=None):
@ -1178,15 +1175,6 @@ class procurement_order(osv.osv):
return True
#def action_po_assign(self, cr, uid, ids, context=None):
# """ This is action which call from workflow to assign purchase order to procurements
# @return: True
# """
# res = self.make_po(cr, uid, ids, context=context)
# res = res.values()
# return len(res) and res[0] or 0 #TO CHECK: why workflow is generated error if return not integer value
def create_procurement_purchase_order(self, cr, uid, procurement, po_vals, line_vals, context=None):
"""Create the purchase order from the procurement, using
the provided field values, after adding the given purchase
@ -1267,7 +1255,6 @@ class procurement_order(osv.osv):
'product_uom': uom_id,
'price_unit': price or 0.0,
'date_planned': schedule_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'move_dest_id': procurement.move_dest_id and procurement.move_dest_id.id or False,
'taxes_id': [(6, 0, taxes)],
}
@ -1282,6 +1269,7 @@ class procurement_order(osv.osv):
seq_obj = self.pool.get('ir.sequence')
pass_ids = []
linked_po_ids = []
sum_po_line_ids = []
for procurement in self.browse(cr, uid, ids, context=context):
partner = self._get_product_supplier(cr, uid, procurement, context=context)
if not partner:
@ -1296,9 +1284,17 @@ class procurement_order(osv.osv):
('location_id', '=', procurement.location_id.id), ('company_id', '=', procurement.company_id.id)], context=context)
if available_draft_po_ids:
po_id = available_draft_po_ids[0]
line_vals.update(order_id=po_id)
po_line_id = po_line_obj.create(cr, uid, line_vals, context=context)
linked_po_ids.append(procurement.id)
#look for any other PO line in the selected PO with same product and UoM to sum quantities instead of creating a new po line
available_po_line_ids = po_line_obj.search(cr, uid, [('order_id', '=', po_id), ('product_id', '=', line_vals['product_id']), ('product_uom', '=', line_vals['product_uom'])], context=context)
if available_po_line_ids:
po_line = po_line_obj.browse(cr, uid, available_po_line_ids[0], context=context)
po_line_obj.write(cr, uid, po_line.id, {'product_qty': po_line.product_qty + line_vals['product_qty']}, context=context)
po_line_id = po_line.id
sum_po_line_ids.append(procurement.id)
else:
line_vals.update(order_id=po_id)
po_line_id = po_line_obj.create(cr, uid, line_vals, context=context)
linked_po_ids.append(procurement.id)
else:
purchase_date = self._get_purchase_order_date(cr, uid, procurement, company, schedule_date, context=context)
name = seq_obj.get(cr, uid, 'purchase.order') or _('PO: %s') % procurement.name
@ -1323,6 +1319,8 @@ class procurement_order(osv.osv):
self.message_post(cr, uid, pass_ids, body=_("Draft Purchase Order created"), context=context)
if linked_po_ids:
self.message_post(cr, uid, linked_po_ids, body=_("Purchase line created and linked to an existing Purchase Order"), context=context)
if sum_po_line_ids:
self.message_post(cr, uid, sum_po_line_ids, body=_("Quantity added in existing Purchase Order Line"), context=context)
return res
def _product_virtual_get(self, cr, uid, order_point):

View File

@ -189,9 +189,7 @@
<record id="trans_picking_except_picking" model="workflow.transition">
<field name="act_from" ref="act_picking"/>
<field name="act_to" ref="act_except_picking"/>
<field name="trigger_model">stock.move</field>
<field name="trigger_expr_id">move_lines_get()</field>
<field name="condition">test_moves_except()</field>
<field name="signal">picking_cancel</field>
</record>
<record id="trans_invoice_except_invoice" model="workflow.transition">
<field name="act_from" ref="act_invoice"/>
@ -201,9 +199,7 @@
<record id="trans_picking_picking_done" model="workflow.transition">
<field name="act_from" ref="act_picking"/>
<field name="act_to" ref="act_picking_done"/>
<field name="trigger_model">stock.move</field>
<field name="trigger_expr_id">move_lines_get()</field>
<field name="condition">test_moves_done()</field>
<field name="signal">picking_done</field>
</record>
<record id="trans_invoice_invoice_done" model="workflow.transition">
@ -225,33 +221,5 @@
<field name="condition">invoiced</field>
</record>
<!-- Procurement -->
<!-- TODO csn : check if fix is needed since no more workflow in procurement -->
<!-- <record id="act_buy" model="workflow.activity">
<field name="wkf_id" ref="procurement.wkf_procurement"/>
<field name="name">buy</field>
<field name="kind">subflow</field>
<field name="subflow_id" search="[('osv','=','purchase.order')]"/>
<field name="action">action_po_assign()</field>
</record>
<record id="trans_confirm_mto_purchase" model="workflow.transition">
<field name="act_from" ref="procurement.act_confirm_mto"/>
<field name="act_to" ref="act_buy"/>
<field name="condition">check_buy() and check_supplier_info() and not check_product_requisition()</field>
</record>
<record id="trans_buy_make_done" model="workflow.transition">
<field name="act_from" ref="act_buy"/>
<field name="act_to" ref="procurement.act_make_done"/>
<field name="signal">subflow.delivery_done</field>
</record>
<record id="trans_buy_cancel" model="workflow.transition">
<field name="act_from" ref="act_buy"/>
<field name="act_to" ref="procurement.act_cancel"/>
<field name="signal">subflow.cancel</field>
</record>
-->
</data>
</openerp>

View File

@ -35,10 +35,26 @@ class stock_move(osv.osv):
ids = [ids]
res = super(stock_move, self).write(cr, uid, ids, vals, context=context)
from openerp import workflow
for id in ids:
workflow.trg_trigger(uid, 'stock.move', id, cr)
if 'state' in vals:
for move in self.browse(cr, uid, ids, context=context):
if move.purchase_line_id and move.purchase_line_id.order_id:
order_id = move.purchase_line_id.order_id.id
if self.pool.get('purchase.order').test_moves_done(cr, uid, [order_id], context=context):
workflow.trg_validate(uid, 'purchase.order', order_id, 'picking_done', cr)
if self.pool.get('purchase.order').test_moves_except(cr, uid, [order_id], context=context):
workflow.trg_validate(uid, 'purchase.order', order_id, 'picking_cancel', cr)
return res
def copy(self, cr, uid, id, default=None, context=None):
if not default:
default = {}
if not default.get('split_from'):
#we don't want to propagate the link to the purchase order line except in case of move split
default.update({
'purchase_line_id': False,
})
return super(stock_move, self).copy(cr, uid, id, default, context)
class stock_picking(osv.osv):
_inherit = 'stock.picking'
@ -148,11 +164,12 @@ class stock_warehouse(osv.osv):
break
return res
def _handle_renaming(self, cr, uid, warehouse, name, context=None):
res = super(stock_warehouse, self)._handle_renaming(cr, uid, warehouse, name, context=context)
def _handle_renaming(self, cr, uid, warehouse, name, code, context=None):
res = super(stock_warehouse, self)._handle_renaming(cr, uid, warehouse, name, code, context=context)
pull_obj = self.pool.get('procurement.rule')
#change the buy pull rule name
pull_obj.write(cr, uid, warehouse.buy_pull_id.id, {'name': warehouse.buy_pull_id.name.replace(warehouse.name, name, 1)}, context=context)
if warehouse.buy_pull_id:
pull_obj.write(cr, uid, warehouse.buy_pull_id.id, {'name': warehouse.buy_pull_id.name.replace(warehouse.name, name, 1)}, context=context)
return res
def change_route(self, cr, uid, ids, warehouse, new_reception_step=False, new_delivery_step=False, context=None):

View File

@ -52,7 +52,7 @@ class purchase_requisition(osv.osv):
'purchase_ids': fields.one2many('purchase.order', 'requisition_id', 'Purchase Orders', states={'done': [('readonly', True)]}),
'po_line_ids': fields.function(_get_po_line, method=True, type='one2many', relation='purchase.order.line', string='Products by supplier'),
'line_ids': fields.one2many('purchase.requisition.line', 'requisition_id', 'Products to Purchase', states={'done': [('readonly', True)]}),
'move_dest_id': fields.many2one('stock.move', 'Reservation Destination', ondelete='set null'),
'procurement_id': fields.many2one('procurement.order', 'Procurement', ondelete='set null'),
'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse'),
'state': fields.selection([('draft', 'Draft'), ('in_progress', 'Confirmed'), ('open', 'Bid Selection'), ('done', 'PO Created'), ('cancel', 'Cancelled')],
'Status', track_visibility='onchange', required=True),
@ -169,7 +169,6 @@ class purchase_requisition(osv.osv):
vals.update({
'order_id': purchase_id,
'product_id': product.id,
'move_dest_id': requisition.move_dest_id.id,
'account_analytic_id': requisition_line.account_analytic_id.id,
})
return vals
@ -363,6 +362,13 @@ class purchase_order(osv.osv):
default.update({'requisition_id': False})
return super(purchase_order, self).copy(cr, uid, id, default=default, context=context)
def _prepare_order_line_move(self, cr, uid, order, order_line, picking_id, group_id, context=None):
stock_move_lines = super(purchase_order, self)._prepare_order_line_move(cr, uid, order, order_line, picking_id, group_id, context=context)
if order.requisition_id and order.requisition_id.procurement_id and order.requisition_id.procurement_id.move_dest_id:
for i in range(0, len(stock_move_lines)):
stock_move_lines[i]['move_dest_id'] = order.requisition_id.procurement_id.move_dest_id.id
return stock_move_lines
class purchase_order_line(osv.osv):
_inherit = 'purchase.order.line'
@ -410,7 +416,7 @@ class procurement_order(osv.osv):
'date_end': procurement.date_planned,
'warehouse_id': warehouse_id and warehouse_id[0] or False,
'company_id': procurement.company_id.id,
'move_dest_id': procurement.move_dest_id and procurement.move_dest_id.id or False,
'procurement_id': procurement.id,
'line_ids': [(0, 0, {
'product_id': procurement.product_id.id,
'product_uom_id': procurement.product_uom.id,
@ -418,8 +424,17 @@ class procurement_order(osv.osv):
})],
})
self.message_post(cr, uid, [procurement.id], body=_("Purchase Requisition created"), context=context)
return self.write(cr, uid, [procurement.id], {'requisition_id': requisition_id}, context=context)
return super(procurement_order, self)._run(cr, uid, procurement, context=context)
def _check(self, cr, uid, procurement, context=None):
requisition_obj = self.pool.get('purchase.requisition')
if procurement.rule_id and procurement.rule_id.action == 'buy' and procurement.product_id.purchase_requisition:
if procurement.requisition_id.state == 'done':
if any([purchase.shipped for purchase in procurement.requisition_id.purchase_ids]):
return True
return False
return super(procurement_order, self)._check(cr, uid, procurement, context=context)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -48,7 +48,8 @@
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem action="action_header_img" id="menu_header_img" parent="base.menu_custom" sequence="14"/>
<menuitem id="menu_webkit" name="Webkit" parent="base.menu_custom"/>
<menuitem action="action_header_img" id="menu_header_img" parent="menu_webkit" sequence="14"/>
<record id="action_header_webkit" model="ir.actions.act_window">
<field name="name">Webkit Headers/Footers</field>
@ -56,7 +57,7 @@
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem action="action_header_webkit" id="menu_header_webkit" parent="base.menu_custom" sequence="14"/>
<menuitem action="action_header_webkit" id="menu_header_webkit" parent="menu_webkit" sequence="14"/>
</data>
</openerp>

View File

@ -301,7 +301,7 @@
<field name="monthly_invoiced" widget="gauge" style="width:160px; height: 120px; cursor: pointer;"
options="{'max_field': 'invoiced_target'}">Invoiced</field>
<field name="invoiced_forecast" widget="gauge" style="width:160px; height: 120px; cursor: pointer;"
options="{'max_field': 'invoiced_target', 'action_change': 'action_forecast'}">Forecast</field>
options="{'max_field': 'invoiced_target', 'on_change': 'action_forecast'}">Forecast</field>
</div>
<div class="oe_center oe_salesteams_help" style="color:#bbbbbb;" t-if="!record.invoiced_target.raw_value">
<br/>Define an invoicing target in the sales team settings to see the period's achievement and forecast at a glance.

View File

@ -44,7 +44,9 @@
I add the routes manufacture and mto to the product
-
!python {model: product.product, id: scheduler_product}: |
self.write(cr, uid, [ref("product_product_slidermobile0")], {"route_ids": [(4, ref("mrp.route_warehouse0_manufacture")), (4, ref("stock.route_warehouse0_mto"))]})
route_warehouse0_manufacture = self.pool.get('stock.warehouse').browse(cr, uid, ref('stock.warehouse0')).manufacture_pull_id.route_id.id
route_warehouse0_mto = self.pool.get('stock.warehouse').browse(cr, uid, ref('stock.warehouse0')).mto_pull_id.route_id.id
self.write(cr, uid, ref('product_product_slidermobile0'), { 'route_ids': [(6, 0, [route_warehouse0_mto,route_warehouse0_manufacture])]}, context=context)
-
I create a Bill of Material record for Slider Mobile
-

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import test_move_explode
checks = [
test_move_explode,
]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.tests import common
class TestMoveExplode(common.TransactionCase):
def setUp(self):
super(TestMoveExplode, self).setUp()
cr, uid = self.cr, self.uid
# Usefull models
self.ir_model_data = self.registry('ir.model.data')
self.sale_order_line = self.registry('sale.order.line')
self.sale_order = self.registry('sale.order')
self.mrp_bom = self.registry('mrp.bom')
#product that has a phantom bom
self.product_bom_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_product_4')[1]
#bom with that product
self.bom_id = self.ir_model_data.get_object_reference(cr, uid, 'mrp', 'mrp_bom_24')[1]
#partner agrolait
self.partner_id = self.ir_model_data.get_object_reference(cr, uid, 'base', 'res_partner_1')[1]
def test_00_sale_move_explode(self):
"""check that when creating a sale order with a product that has a phantom BoM, move explode into content of the
BoM"""
cr, uid, context = self.cr, self.uid, {}
#create sale order with one sale order line containing product with a phantom bom
so_id = self.sale_order.create(cr, uid, vals={'partner_id': self.partner_id}, context=context)
self.sale_order_line.create(cr, uid, values={'order_id': so_id, 'product_id': self.product_bom_id, 'product_uom_qty': 1}, context=context)
#confirm sale order
self.sale_order.action_button_confirm(cr, uid, [so_id], context=context)
#get all move associated to that sale_order
browse_move_ids = self.sale_order.browse(cr, uid, so_id, context=context).picking_ids[0].move_lines
move_ids = [x.id for x in browse_move_ids]
#we should have same amount of move as the component in the phatom bom
bom = self.mrp_bom.browse(cr, uid, self.bom_id, context=context)
bom_component_length = self.mrp_bom._bom_explode(cr, uid, bom, 1, [])
self.assertEqual(len(move_ids), len(bom_component_length[0]))

View File

@ -30,17 +30,6 @@
</xpath>
</field>
</record>
<record id="stock_location_route_tree_inherit" model="ir.ui.view">
<field name="name">stock.location.route.tree.inherit</field>
<field name="inherit_id" ref="stock.stock_location_route_tree"/>
<field name="model">stock.location.route</field>
<field name="arch" type="xml">
<xpath expr="//field[@name='warehouse_selectable']" position="after">
<field name="sale_selectable"/>
</xpath>
</field>
</record>
</data>
</openerp>

View File

@ -3,6 +3,17 @@
-
!context
uid: 'res_sale_stock_salesman'
-
Create a new SO to be sure we don't have one with product that can explode in mrp
-
!record {model: sale.order, id: sale_order_service}:
partner_id: base.res_partner_18
partner_invoice_id: base.res_partner_18
partner_shipping_id: base.res_partner_18
user_id: base.user_root
pricelist_id: product.list0
warehouse_id: stock.warehouse0
order_policy: picking
-
Add SO line with service type product in SO to check flow which contain service type product in SO(BUG#1167330).
-
@ -12,27 +23,37 @@
product_uom_qty: 1.0
product_uom: 1
price_unit: 150.0
order_id: sale.sale_order_6
order_id: sale_order_service
-
Add a second SO line with a normal product
-
!record {model: sale.order.line, id: sale_order_2}:
name: 'Mouse Optical'
product_id: product.product_product_10
product_uom_qty: 1.0
product_uom: 1
price_unit: 150.0
order_id: sale_order_service
-
First I check the total amount of the Quotation before Approved.
-
!assert {model: sale.order, id: sale.sale_order_6, string: The amount of the Quotation is not correctly computed}:
!assert {model: sale.order, id: sale_order_service, string: The amount of the Quotation is not correctly computed}:
- sum([l.price_subtotal for l in order_line]) == amount_untaxed
-
I set an explicit invoicing partner that is different from the main SO Customer
-
!python {model: sale.order, id: sale.sale_order_6}: |
order = self.browse(cr, uid, ref("sale.sale_order_6"))
!python {model: sale.order, id: sale_order_service}: |
order = self.browse(cr, uid, ref("sale_order_service"))
order.write({'partner_invoice_id': ref('base.res_partner_address_29')})
-
I confirm the quotation with Invoice based on deliveries policy.
-
!workflow {model: sale.order, action: order_confirm, ref: sale.sale_order_6}
!workflow {model: sale.order, action: order_confirm, ref: sale_order_service}
-
I check that invoice should not created before dispatch delivery.
-
!python {model: sale.order}: |
order = self.pool.get('sale.order').browse(cr, uid, ref("sale.sale_order_6"))
order = self.pool.get('sale.order').browse(cr, uid, ref("sale_order_service"))
assert order.state == 'progress', 'Order should be in inprogress.'
assert len(order.invoice_ids) == False, "Invoice should not created."
-
@ -42,7 +63,7 @@
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
order = self.browse(cr, uid, ref("sale.sale_order_6"))
order = self.browse(cr, uid, ref("sale_order_service"))
for order_line in order.order_line:
if order_line.product_id.type == 'product':
procurement = order_line.procurement_ids[0]
@ -74,7 +95,7 @@
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
sale_order = self.browse(cr, uid, ref("sale.sale_order_6"))
sale_order = self.browse(cr, uid, ref("sale_order_service"))
assert sale_order.picking_ids, "Delivery order is not created."
for picking in sale_order.picking_ids:
assert picking.state == "auto" or "confirmed", "Delivery order should be in 'Waitting Availability' state."
@ -103,7 +124,7 @@
Now, I dispatch delivery order.
-
!python {model: stock.picking}: |
order = self.pool.get('sale.order').browse(cr, uid, ref("sale.sale_order_6"))
order = self.pool.get('sale.order').browse(cr, uid, ref("sale_order_service"))
for pick in order.picking_ids:
data = pick.force_assign()
if data == True:
@ -117,7 +138,7 @@
I check sale order to verify shipment.
-
!python {model: sale.order}: |
order = self.pool.get('sale.order').browse(cr, uid, ref("sale.sale_order_6"))
order = self.pool.get('sale.order').browse(cr, uid, ref("sale_order_service"))
assert order.shipped == True, "Sale order is not Delivered."
#assert order.state == 'progress', 'Order should be in inprogress.'
assert len(order.invoice_ids) == False, "Invoice should not created on dispatch delivery order."
@ -126,7 +147,7 @@
-
!python {model: stock.invoice.onshipping}: |
sale = self.pool.get('sale.order')
sale_order = sale.browse(cr, uid, ref("sale.sale_order_6"))
sale_order = sale.browse(cr, uid, ref("sale_order_service"))
ship_ids = [x.id for x in sale_order.picking_ids]
wiz_id = self.create(cr, uid, {'journal_id': ref('account.sales_journal')},
{'active_ids': ship_ids, 'active_model': 'stock.picking'})
@ -135,7 +156,7 @@
I check the invoice details after dispatched delivery.
-
!python {model: sale.order}: |
order = self.browse(cr, uid, ref("sale.sale_order_6"))
order = self.browse(cr, uid, ref("sale_order_service"))
assert order.invoice_ids, "Invoice is not created."
ac = order.partner_invoice_id.property_account_receivable.id
journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'sale'), ('company_id', '=', order.company_id.id)])
@ -166,7 +187,7 @@
I open the Invoice.
-
!python {model: sale.order}: |
so = self.browse(cr, uid, ref("sale.sale_order_6"))
so = self.browse(cr, uid, ref("sale_order_service"))
account_invoice_obj = self.pool.get('account.invoice')
for invoice in so.invoice_ids:
account_invoice_obj.signal_invoice_open(cr, uid, [invoice.id])
@ -175,7 +196,7 @@
-
!python {model: account.invoice}: |
sale_order = self.pool.get('sale.order')
order = sale_order.browse(cr, uid, ref("sale.sale_order_6"))
order = sale_order.browse(cr, uid, ref("sale_order_service"))
journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'cash'), ('company_id', '=', order.company_id.id)], limit=1)
for invoice in order.invoice_ids:
invoice.pay_and_reconcile(
@ -192,7 +213,7 @@
I check the order after paid invoice.
-
!python {model: sale.order}: |
order = self.browse(cr, uid, ref("sale.sale_order_6"))
order = self.browse(cr, uid, ref("sale_order_service"))
assert order.invoiced == True, "Sale order is not invoiced."
assert order.invoiced_rate == 100, "Invoiced progress is not 100%."
assert order.state == 'done', 'Order should be in closed.'
@ -203,7 +224,7 @@
import os
import openerp.report
from openerp import tools
data, format = openerp.report.render_report(cr, uid, [ref('sale.sale_order_6')], 'sale.order', {}, {})
data, format = openerp.report.render_report(cr, uid, [ref('sale_order_service')], 'sale.order', {}, {})
if tools.config['test_report_directory']:
file(os.path.join(tools.config['test_report_directory'], 'sale-sale_order.'+format), 'wb+').write(data)

View File

@ -216,8 +216,9 @@ class procurement_order(osv.osv):
move_obj = self.pool.get('stock.move')
move_dict = self._run_move_create(cr, uid, procurement, context=context)
move_id = move_obj.create(cr, uid, move_dict, context=context)
self.message_post(cr, uid, [procurement.id], body=_("Supply Move created"), context=context)
move_obj.action_confirm(cr, uid, [move_id], context=context)
return move_id
return True
return super(procurement_order, self)._run(cr, uid, procurement, context)
def _check(self, cr, uid, procurement, context=None):

View File

@ -74,7 +74,7 @@ function openerp_picking_widgets(instance){
_.each( ops, function(op){
rows.push({
cols: {
product: op.product_id[1],
product: (op.package_id ? op.package_id[1] : op.product_id[1]) + (op.lot_id ? ' Lot: ' + op.lot_id[1] : ''),
uom: op.product_uom ? product_uom[1] : '',
qty: op.product_qty,
},
@ -90,9 +90,14 @@ function openerp_picking_widgets(instance){
this._super();
var model = this.getParent();
this.$('.js_pack_op').click(function(){
self.$('.js_pack_op').removeClass('oe_selected');
$(this).addClass('oe_selected');
model.set_selected_operation(parseInt($(this).attr('op-id')));
if (!this.classList.contains('oe_selected')){
self.$('.js_pack_op').removeClass('oe_selected');
$(this).addClass('oe_selected');
model.set_selected_operation(parseInt($(this).attr('op-id')));
} else {
$(this).removeClass('oe_selected');
model.set_selected_operation(null);
};
});
},
});
@ -628,14 +633,7 @@ function openerp_picking_widgets(instance){
if( this.selected_operation.picking_id === this.picking.id && this.selected_operation.id ){
return this.selected_operation.id;
}else{
this.selected_operation.picking_id = this.picking.id;
var ops = this.get_current_operations();
if(ops.length === 0){
this.selected_operation.id = null;
}else{
this.selected_operation.id = ops[ops.length - 1].id;
}
return this.selected_operation.id;
return null;
}
},
reset_selected_operation: function(){
@ -651,16 +649,19 @@ function openerp_picking_widgets(instance){
var self = this;
var op = this.get_selected_operation();
if( !op ){
return;
//TODO typing the ean of a product manually ?
//(scanning the barcode is already handled somewhere else, and i don't know how to differenciate the 2 operations)
// and the result is that if i uncomment the next line, scanning a product counts it twice
//self.scan(quantity);
}
if(typeof quantity === 'number' && quantity >= 0){
else {if(typeof quantity === 'number' && quantity >= 0){
new instance.web.Model('stock.pack.operation')
.call('write',[[op],{'product_qty': quantity }])
.then(function(){
self.refresh_ui(self.picking.id);
});
}
}}
},
connect_numpad: function(){

View File

@ -82,6 +82,19 @@ class stock_location(osv.osv):
context_with_inactive['active_test'] = False
return self.search(cr, uid, [('id', 'child_of', ids)], context=context_with_inactive)
def _name_get(self, cr, uid, location, context=None):
name = location.name
while location.location_id and location.usage != 'view':
location = location.location_id
name = location.name + '/' + name
return name
def name_get(self, cr, uid, ids, context=None):
res = []
for location in self.browse(cr, uid, ids, context=context):
res.append((location.id, self._name_get(cr, uid, location, context=context)))
return res
_columns = {
'name': fields.char('Location Name', size=64, required=True, translate=True),
'active': fields.boolean('Active', help="By unchecking the active field, you may hide a location without deleting it."),
@ -306,7 +319,7 @@ class stock_quant(osv.osv):
self.write(cr, SUPERUSER_ID, toreserve, {'reservation_id': move.id, 'link_move_operation_id': link and link.id or False}, context=context)
#check if move'state needs to be set as 'assigned'
move.refresh()
if sum([q.qty for q in move.reserved_quant_ids]) == move.product_qty and move.state in ('confirmed', 'waiting'):
if move.reserved_availability == move.product_qty and move.state in ('confirmed', 'waiting'):
self.pool.get('stock.move').write(cr, uid, [move.id], {'state': 'assigned'}, context=context)
def quants_move(self, cr, uid, quants, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, context=None):
@ -830,6 +843,7 @@ class stock_picking(osv.osv):
for pick in self.browse(cr, uid, ids, context=context):
if pick.state == 'draft':
self.action_confirm(cr, uid, [pick.id], context=context)
pick.refresh()
#skip the moves that don't need to be checked
move_ids = [x.id for x in pick.move_lines if x.state not in ('draft', 'cancel', 'done')]
if not move_ids:
@ -864,9 +878,7 @@ class stock_picking(osv.osv):
todo = []
for move in pick.move_lines:
if move.state == 'draft':
self.pool.get('stock.move').action_confirm(cr, uid, [move.id],
context=context)
todo.append(move.id)
todo.extend(self.pool.get('stock.move').action_confirm(cr, uid, [move.id], context=context))
elif move.state in ('assigned', 'confirmed'):
todo.append(move.id)
if len(todo):
@ -921,83 +933,103 @@ class stock_picking(osv.osv):
self.do_prepare_partial(cr, uid, picking_ids, context=context)
def do_prepare_partial(self, cr, uid, picking_ids, context=None):
#TODO refactore me
context = context or {}
pack_operation_obj = self.pool.get('stock.pack.operation')
pack_obj = self.pool.get("stock.quant.package")
quant_obj = self.pool.get("stock.quant")
#get list of existing packages and delete them
#get list of existing operations and delete them
existing_package_ids = pack_operation_obj.search(cr, uid, [('picking_id', 'in', picking_ids)], context=context)
if existing_package_ids:
pack_operation_obj.unlink(cr, uid, existing_package_ids, context)
for picking in self.browse(cr, uid, picking_ids, context=context):
packages = []
reserved_move = {}
qtys_remaining = {}
#Calculate packages, reserved quants, qtys of this picking's moves
for move in picking.move_lines:
if move.state not in ('assigned', 'confirmed'):
continue
packages += [x.package_id for x in move.reserved_quant_ids if x.package_id]
reserved_move[move.id] = set([x.id for x in move.reserved_quant_ids])
if move.state == 'assigned':
qty = move.product_qty
else:
qty = move.reserved_availability
#Check which of the reserved quants are entirely in packages (can be in separate method)
packages = list(set([x.package_id for x in move.reserved_quant_ids if x.package_id]))
done_packages = []
for pack in packages:
cont = True
good_pack = False
test_pack = pack
while cont:
quants = pack_obj.get_content(cr, uid, [test_pack.id], context=context)
if all([x.reservation_id.id == move.id for x in quant_obj.browse(cr, uid, quants, context=context) if x.reservation_id]):
good_pack = test_pack.id
if test_pack.parent_id:
test_pack = test_pack.parent_id
else:
cont = False
#Add qty to qtys remaining
if qtys_remaining.get(move.product_id.id):
qtys_remaining[move.product_id.id] += qty
else:
qtys_remaining[move.product_id.id] = qty
# Try to find as much as possible top-level packages that can be moved
top_lvl_packages = set()
for pack in packages:
loop = True
good_pack = False
test_pack = pack
while loop:
quants = pack_obj.get_content(cr, uid, [test_pack.id], context=context)
move_list = [m.id for m in picking.move_lines]
if all([(x.reservation_id and x.reservation_id.id in move_list or False) for x in quant_obj.browse(cr, uid, quants, context=context)]):
good_pack = test_pack.id
if test_pack.parent_id:
test_pack = test_pack.parent_id
else:
cont = False
if good_pack:
done_packages.append(good_pack)
done_packages = list(set(done_packages))
#stop the loop when there's no parent package anymore
loop = False
else:
#stop the loop when the package test_pack is not totally reserved for moves of this picking
#(some quants may be reserved for other picking or not reserved at all)
loop = False
if good_pack:
top_lvl_packages.add(good_pack)
#Create package operations
reserved = set([x.id for x in move.reserved_quant_ids])
remaining_qty = move.product_qty
for pack in pack_obj.browse(cr, uid, done_packages, context=context):
quantl = pack_obj.get_content(cr, uid, [pack.id], context=context)
for quant in quant_obj.browse(cr, uid, quantl, context=context):
remaining_qty -= quant.qty
quants = set(pack_obj.get_content(cr, uid, [pack.id], context=context))
reserved -= quants
pack_operation_obj.create(cr, uid, {
# Create pack operations for the top-level packages found
for pack in pack_obj.browse(cr, uid, list(top_lvl_packages), context=context):
quants = pack_obj.get_content(cr, uid, [pack.id], context=context)
for quant in quant_obj.browse(cr, uid, quants, context=context):
reserved_move[quant.reservation_id.id] -= set([quant.id])
qtys_remaining[quant.product_id.id] -= quant.qty
pack_operation_obj.create(cr, uid, {
'picking_id': picking.id,
'package_id': pack.id,
'product_qty': 1.0,
}, context=context)
yet_to_reserve = list(reserved)
#Create operations based on quants
for quant in quant_obj.browse(cr, uid, yet_to_reserve, context=context):
qty = min(quant.qty, move.product_qty)
remaining_qty -= qty
pack_operation_obj.create(cr, uid, {
'picking_id': picking.id,
'product_qty': qty,
'product_id': quant.product_id.id,
'lot_id': quant.lot_id and quant.lot_id.id or False,
'product_uom_id': quant.product_id.uom_id.id,
'owner_id': quant.owner_id and quant.owner_id.id or False,
'cost': quant.cost,
'package_id': quant.package_id and quant.package_id.id or False,
}, context=context)
if move.state == 'assigned' and remaining_qty > 0:
#only add a line with remaining qty if we have all qty reserved (move is assigned)
#otherwise we may be in a case where we do a partial delivery with only a few
#available products and we don't want to propose to transfer everything
pack_operation_obj.create(cr, uid, {
'picking_id': picking.id,
'product_qty': remaining_qty,
'product_id': move.product_id.id,
'product_uom_id': move.product_id.uom_id.id,
'cost': move.product_id.standard_price,
}, context=context)
# Go through all remaining reserved quants and group by product, package, lot, owner
qtys_grouped = {}
for move, quant_set in reserved_move.items():
for quant in quant_obj.browse(cr, uid, list(quant_set), context=context):
qtys_remaining[quant.product_id.id] -= quant.qty
key = (quant.product_id.id, quant.package_id.id, quant.lot_id.id, quant.owner_id.id)
if qtys_grouped.get(key):
qtys_grouped[key] += quant.qty
else:
qtys_grouped[key] = quant.qty
# Add remaining qtys (in cases of force_assign for example)
for product in qtys_remaining.keys():
if qtys_remaining[product] > 0:
key = (product, False, False, False)
if qtys_grouped.get(key):
qtys_grouped[key] += qtys_remaining[product]
else:
qtys_grouped[key] = qtys_remaining[product]
# Create the necessary operations for the grouped quants and remaining qtys
for key, qty in qtys_grouped.items():
pack_operation_obj.create(cr, uid, {
'picking_id': picking.id,
'product_qty': qty,
'product_id': key[0],
'package_id': key[1],
'lot_id': key[2],
'owner_id': key[3],
'product_uom_id': self.pool.get("product.product").browse(cr, uid, key[0], context=context).uom_id.id,
}, context=context)
def do_unreserve(self, cr, uid, picking_ids, context=None):
"""
@ -1082,14 +1114,11 @@ class stock_picking(osv.osv):
if move.remaining_qty == 0:
if move.state in ('draft', 'assigned', 'confirmed'):
todo_move_ids.append(move.id)
elif move.remaining_qty > 0:
elif move.remaining_qty > 0 and move.remaining_qty < move.product_qty:
new_move = stock_move_obj.split(cr, uid, move, move.remaining_qty, context=context)
todo_move_ids.append(move.id)
#Assign move as it was assigned before
toassign_move_ids.append(new_move)
elif move.state:
#this should never happens
raise
self.rereserve_quants(cr, uid, picking, move_ids=todo_move_ids, context=context)
if todo_move_ids and not context.get('do_only_split'):
self.pool.get('stock.move').action_done(cr, uid, todo_move_ids, context=context)
@ -1150,7 +1179,7 @@ class stock_picking(osv.osv):
product_obj = self.pool.get('product.product')
stock_operation_obj = self.pool.get('stock.pack.operation')
#check if the barcode correspond to a product
matching_product_ids = product_obj.search(cr, uid, [('ean13', '=', barcode_str)], context=context)
matching_product_ids = product_obj.search(cr, uid, ['|', ('ean13', '=', barcode_str), ('default_code', '=', barcode_str)], context=context)
if matching_product_ids:
self.process_product_id_from_ui(cr, uid, picking_id, matching_product_ids[0], context=context)
@ -1472,6 +1501,7 @@ class stock_move(osv.osv):
default = default.copy()
default['move_orig_ids'] = []
default['quant_ids'] = []
default['move_dest_id'] = False
default['reserved_quant_ids'] = []
default['returned_move_ids'] = []
default['linked_move_operation_ids'] = []
@ -1769,28 +1799,40 @@ class stock_move(osv.osv):
""" Confirms stock move or put it in waiting if it's linked to another move.
@return: List of ids.
"""
if isinstance(ids, (int, long)):
ids = [ids]
states = {
'confirmed': [],
'waiting': []
}
for move in self.browse(cr, uid, ids, context=context):
state = 'confirmed'
for m in move.move_orig_ids:
if m.state not in ('done', 'cancel'):
state = 'waiting'
#if the move is preceeded, then it's waiting (if preceeding move is done, then action_assign has been called already and its state is already available)
if move.move_orig_ids:
state = 'waiting'
#if the move is split and some of the ancestor was preceeded, then it's waiting as well
elif move.split_from:
move2 = move.split_from
while move2 and state != 'waiting':
if move2.move_orig_ids:
state = 'waiting'
move2 = move2.split_from
states[state].append(move.id)
self._picking_assign(cr, uid, move, context=context)
for move in self.browse(cr, uid, states['confirmed'], context=context):
if move.procure_method == 'make_to_order':
self._create_procurement(cr, uid, move, context=context)
states['waiting'].append(move.id)
states['confirmed'].remove(move.id)
for state, write_ids in states.items():
if len(write_ids):
self.write(cr, uid, write_ids, {'state': state})
if state == 'confirmed':
for move in self.browse(cr, uid, write_ids, context=context):
if move.procure_method == 'make_to_order':
self._create_procurement(cr, uid, move, context=context)
moves = self.browse(cr, uid, ids, context=context)
self._push_apply(cr, uid, moves, context=context)
return True
return ids
def force_assign(self, cr, uid, ids, context=None):
""" Changes the state to assigned.
@ -1819,40 +1861,72 @@ class stock_move(osv.osv):
context = context or {}
quant_obj = self.pool.get("stock.quant")
to_assign_moves = []
prefered_domain = {}
fallback_domain = {}
main_domain = {}
todo_moves = []
operations = set()
for move in self.browse(cr, uid, ids, context=context):
if move.state not in ('confirmed', 'waiting', 'assigned'):
continue
if move.picking_type_id and move.picking_type_id.auto_force_assign:
to_assign_moves.append(move.id)
#in case the move is returned, we want to try to find quants before forcing the assignment
if not move.origin_returned_move_id:
continue
if move.product_id.type == 'consu':
to_assign_moves.append(move.id)
continue
else:
todo_moves.append(move)
#build the prefered domain based on quants that moved in previous linked done move
prev_quant_ids = []
for m2 in move.move_orig_ids:
for q in m2.quant_ids:
prev_quant_ids.append(q.id)
prefered_domain = prev_quant_ids and [('id', 'in', prev_quant_ids)] or []
fallback_domain = prev_quant_ids and [('id', 'not in', prev_quant_ids)] or []
prefered_domain[move.id] = prev_quant_ids and [('id', 'in', prev_quant_ids)] or []
fallback_domain[move.id] = prev_quant_ids and [('id', 'not in', prev_quant_ids)] or []
#we always keep the quants already assigned and try to find the remaining quantity on quants not assigned only
main_domain = [('reservation_id', '=', False), ('qty', '>', 0)]
main_domain[move.id] = [('reservation_id', '=', False), ('qty', '>', 0)]
#if the move is preceeded, restrict the choice of quants in the ones moved previously in original move
move_orig_ids = []
move2 = move
while move2:
#loop on the split_from to find the ancestor of split moves
move_orig_ids += [x.id for x in move2.move_orig_ids]
move2 = move2.split_from
if move_orig_ids:
main_domain[move.id] += [('history_ids', 'in', move_orig_ids)]
#if the move is returned from another, restrict the choice of quants to the ones that follow the returned move
if move.origin_returned_move_id:
main_domain += [('history_ids', 'in', move.origin_returned_move_id.id)]
#first try to find quants based on specific domains given by linked operations
for record in move.linked_move_operation_ids:
domain = main_domain + self.pool.get('stock.move.operation.link').get_specific_domain(cr, uid, record, context=context)
qty_already_assigned = sum([q.qty for q in record.reserved_quant_ids])
qty = record.qty - qty_already_assigned
quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=domain, prefered_domain=prefered_domain, fallback_domain=fallback_domain, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context)
quant_obj.quants_reserve(cr, uid, quants, move, record, context=context)
#then if the move isn't totally assigned, try to find quants without any specific domain
if move.state != 'assigned':
qty_already_assigned = sum([q.qty for q in move.reserved_quant_ids])
qty = move.product_qty - qty_already_assigned
quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=main_domain, prefered_domain=prefered_domain, fallback_domain=fallback_domain, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context)
quant_obj.quants_reserve(cr, uid, quants, move, context=context)
main_domain[move.id] += [('history_ids', 'in', move.origin_returned_move_id.id)]
for link in move.linked_move_operation_ids:
operations.add(link.operation_id)
# Check all ops and sort them: we want to process first the packages, then operations with lot then the rest
operations = list(operations)
operations.sort(key=lambda x: ((x.package_id and not x.product_id) and -4 or 0) + (x.package_id and -2 or 0) + (x.lot_id and -1 or 0))
for ops in operations:
#first try to find quants based on specific domains given by linked operations
for record in ops.linked_move_operation_ids:
move = record.move_id
domain = main_domain[move.id] + self.pool.get('stock.move.operation.link').get_specific_domain(cr, uid, record, context=context)
qty_already_assigned = sum([q.qty for q in record.reserved_quant_ids])
qty = record.qty - qty_already_assigned
quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=domain, prefered_domain=prefered_domain[move.id], fallback_domain=fallback_domain[move.id], restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context)
quant_obj.quants_reserve(cr, uid, quants, move, record, context=context)
#force assignation of consumable products
for move in todo_moves:
#then if the move isn't totally assigned, try to find quants without any specific domain
if move.state != 'assigned':
qty_already_assigned = move.reserved_availability
qty = move.product_qty - qty_already_assigned
quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=main_domain[move.id], prefered_domain=prefered_domain[move.id], fallback_domain=fallback_domain[move.id], restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context)
quant_obj.quants_reserve(cr, uid, quants, move, context=context)
#force assignation of consumable products and picking type auto_force_assign
if to_assign_moves:
self.force_assign(cr, uid, to_assign_moves, context=context)
#check if a putaway rule is likely to be processed and store result on the move
@ -1886,7 +1960,7 @@ class stock_move(osv.osv):
pack_obj = self.pool.get("stock.quant.package")
packs = set()
for move in self.browse(cr, uid, ids, context=context):
packs |= set([q.package_id.id for q in move.quant_ids if q.package_id])
packs |= set([q.package_id.id for q in move.quant_ids if q.package_id and q.qty > 0])
return pack_obj._check_location_constraint(cr, uid, list(packs), context=context)
def action_done(self, cr, uid, ids, context=None):
@ -1898,7 +1972,7 @@ class stock_move(osv.osv):
pack_op_obj = self.pool.get("stock.pack.operation")
todo = [move.id for move in self.browse(cr, uid, ids, context=context) if move.state == "draft"]
if todo:
self.action_confirm(cr, uid, todo, context=context)
ids = self.action_confirm(cr, uid, todo, context=context)
pickings = set()
procurement_ids = []
#Search operations that are linked to the moves
@ -2025,13 +2099,21 @@ class stock_move(osv.osv):
self.action_done(cr, uid, res, context=context)
return res
def split(self, cr, uid, move, qty, restrict_lot_id=False, restrict_partner_id=False, context=None):
""" Splits qty from move move into a new move
:param move: browse record
:param qty: float. quantity to split (given in product UoM)
:param context: dictionay. can contains the special key 'source_location_id' in order to force the source location when copying the move
returns the ID of the backorder move created
"""
if move.state in ('done', 'cancel'):
raise osv.except_osv(_('Error'), _('You cannot split a move done'))
if move.state == 'draft':
#we restrict the split of a draft move because if not confirmed yet, it may be replaced by several other moves in
#case of phantom bom (with mrp module). And we don't want to deal with this complexity by copying the product that will explode.
raise osv.except_osv(_('Error'), _('You cannot split a draft move. It needs to be confirmed first.'))
if move.product_qty <= qty or qty == 0:
return move.id
@ -2041,16 +2123,11 @@ class stock_move(osv.osv):
uom_qty = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, qty, move.product_uom.id)
uos_qty = uom_qty * move.product_uos_qty / move.product_uom_qty
if move.state in ('done', 'cancel'):
raise osv.except_osv(_('Error'), _('You cannot split a move done'))
defaults = {
'product_uom_qty': uom_qty,
'product_uos_qty': uos_qty,
'state': move.state,
'procure_method': 'make_to_stock',
'move_dest_id': False,
'reserved_quant_ids': [],
'restrict_lot_id': restrict_lot_id,
'restrict_partner_id': restrict_partner_id,
'split_from': move.id,
@ -2064,15 +2141,14 @@ class stock_move(osv.osv):
self.write(cr, uid, [move.id], {
'product_uom_qty': move.product_uom_qty - uom_qty,
'product_uos_qty': move.product_uos_qty - uos_qty,
#'reserved_quant_ids': [(6,0,[])] SHOULD NOT CHANGE as it has been reserved already
}, context=ctx)
if move.move_dest_id and move.propagate:
new_move_prop = self.split(cr, uid, move.move_dest_id, qty, context=context)
self.write(cr, uid, [new_move], {'move_dest_id': new_move_prop}, context=context)
self.action_confirm(cr, uid, [new_move], context=context)
return new_move
#returning the first element of list returned by action_confirm is ok because we checked it wouldn't be exploded (and
#thus the result of action_confirm should always be a list of 1 element length)
return self.action_confirm(cr, uid, [new_move], context=context)[0]
class stock_inventory(osv.osv):
@ -2173,8 +2249,8 @@ class stock_inventory(osv.osv):
move_obj = self.pool.get('stock.move')
for inv in self.browse(cr, uid, ids, context=context):
for inventory_line in inv.line_ids:
if inventory_line.product_qty < 0:
raise osv.except_osv(_('Warning'),_('You cannot set a negative product quantity in an inventory line'))
if inventory_line.product_qty < 0 and inventory_line.product_qty != inventory_line.th_qty:
raise osv.except_osv(_('Warning'),_('You cannot set a negative product quantity in an inventory line:\n\t%s - qty: %s' % (inventory_line.product_id.name, inventory_line.product_qty)))
if not inv.move_ids:
self.action_check(cr, uid, [inv.id], context=context)
inv.refresh()
@ -2287,13 +2363,6 @@ class stock_inventory(osv.osv):
for key, value in product_line.items():
if not value:
product_line[key] = False
if product_line['product_qty'] < 0:
summary = _('Product: ') + product_obj.browse(cr, uid, product_line['product_id'], context=context).name + '\n'
summary += _('Location: ') + location_obj.browse(cr, uid, product_line['location_id'], context=context).complete_name + '\n'
summary += (_('Partner: ') + self.pool.get('res.partner').browse(cr, uid, product_line['partner_id'], context=context).name + '\n') if product_line['partner_id'] else ''
summary += (_('Lot: ') + self.pool.get('stock.production.lot').browse(cr, uid, product_line['prod_lot_id'], context=context).name + '\n') if product_line['prod_lot_id'] else ''
summary += (_('Package: ') + self.pool.get('stock.quant.package').browse(cr, uid, product_line['package_id'], context=context).name + '\n') if product_line['package_id'] else ''
raise osv.except_osv(_('Warning'),_('This inventory line has a theoretical negative quantity, please fix it before doing an inventory\n%s' % (summary)))
product_line['inventory_id'] = inventory.id
product_line['th_qty'] = product_line['product_qty']
if product_line['product_id']:
@ -2398,7 +2467,7 @@ class stock_warehouse(osv.osv):
_description = "Warehouse"
_columns = {
'name': fields.char('Name', size=128, required=True, select=True),
'name': fields.char('Warehouse Name', size=128, required=True, select=True),
'company_id': fields.many2one('res.company', 'Company', required=True, select=True),
'partner_id': fields.many2one('res.partner', 'Address'),
'view_location_id': fields.many2one('stock.location', 'View Location', required=True, domain=[('usage', '=', 'view')]),
@ -2756,7 +2825,7 @@ class stock_warehouse(osv.osv):
#create view location for warehouse
wh_loc_id = location_obj.create(cr, uid, {
'name': _(vals.get('name')),
'name': _(vals.get('code')),
'usage': 'view',
'location_id': data_obj.get_object_reference(cr, uid, 'stock', 'stock_location_locations')[1]
}, context=context)
@ -2913,14 +2982,14 @@ class stock_warehouse(osv.osv):
'pick_pack_ship': (_('Pick + Pack + Ship'), [(warehouse.lot_stock_id, warehouse.wh_pack_stock_loc_id, warehouse.pick_type_id.id), (warehouse.wh_pack_stock_loc_id, warehouse.wh_output_stock_loc_id, warehouse.pack_type_id.id), (warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id.id)]),
}
def _handle_renaming(self, cr, uid, warehouse, name, context=None):
def _handle_renaming(self, cr, uid, warehouse, name, code, context=None):
location_obj = self.pool.get('stock.location')
route_obj = self.pool.get('stock.location.route')
pull_obj = self.pool.get('procurement.rule')
push_obj = self.pool.get('stock.location.path')
#rename location
location_id = warehouse.lot_stock_id.location_id.id
location_obj.write(cr, uid, location_id, {'name': name}, context=context)
location_obj.write(cr, uid, location_id, {'name': code}, context=context)
#rename route and push-pull rules
for route in warehouse.route_ids:
route_obj.write(cr, uid, route.id, {'name': route.name.replace(warehouse.name, name, 1)}, context=context)
@ -2948,12 +3017,13 @@ class stock_warehouse(osv.osv):
self.switch_location(cr, uid, warehouse.id, warehouse, vals.get('reception_steps', False), vals.get('delivery_steps', False), context=context)
# switch between route
self.change_route(cr, uid, ids, warehouse, vals.get('reception_steps', False), vals.get('delivery_steps', False), context=context_with_inactive)
warehouse.refresh()
if vals.get('code') or vals.get('name'):
name = warehouse.name
#rename sequence
if vals.get('name'):
name = vals.get('name')
self._handle_renaming(cr, uid, warehouse, name, context=context_with_inactive)
name = vals.get('name', warehouse.name)
self._handle_renaming(cr, uid, warehouse, name, vals.get('code', warehouse.code), context=context_with_inactive)
seq_obj.write(cr, uid, warehouse.in_type_id.sequence_id.id, {'name': name + _(' Sequence in'), 'prefix': vals.get('code', warehouse.code) + '\IN\\'}, context=context)
seq_obj.write(cr, uid, warehouse.out_type_id.sequence_id.id, {'name': name + _(' Sequence out'), 'prefix': vals.get('code', warehouse.code) + '\OUT\\'}, context=context)
seq_obj.write(cr, uid, warehouse.pack_type_id.sequence_id.id, {'name': name + _(' Sequence packing'), 'prefix': vals.get('code', warehouse.code) + '\PACK\\'}, context=context)
@ -3086,6 +3156,7 @@ class stock_location_path(osv.osv):
'propagate': True,
'active': True,
}
def _apply(self, cr, uid, rule, move, context=None):
move_obj = self.pool.get('stock.move')
newdate = (datetime.strptime(move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta.relativedelta(days=rule.delay or 0)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
@ -3101,7 +3172,6 @@ class stock_location_path(osv.osv):
if rule.location_dest_id.id != old_dest_location:
#call again push_apply to see if a next step is defined
move_obj._push_apply(cr, uid, [move], context=context)
return move.id
else:
move_id = move_obj.copy(cr, uid, move.id, {
'location_id': move.location_dest_id.id,
@ -3119,7 +3189,6 @@ class stock_location_path(osv.osv):
'move_dest_id': move_id,
})
move_obj.action_confirm(cr, uid, [move_id], context=None)
return move_id
class stock_move_putaway(osv.osv):
_name = 'stock.move.putaway'
@ -3242,7 +3311,7 @@ class stock_package(osv.osv):
quant_ids = self.get_content(cr, uid, [parent.id], context=context)
quants = quant_obj.browse(cr, uid, quant_ids, context=context)
location_id = quants and quants[0].location_id.id or False
if not all([quant.location_id.id == location_id for quant in quants]):
if not all([quant.location_id.id == location_id for quant in quants if quant.qty > 0]):
raise osv.except_osv(_('Error'), _('Everything inside a package should be in the same location'))
return True
@ -3424,7 +3493,7 @@ class stock_pack_operation(osv.osv):
def recompute_rem_qty_from_operation(self, cr, uid, op_ids, context=None):
def _create_link_for_product(product_id, qty):
qty_to_assign = qty
for move in op.picking_id.move_lines:
for move in sorted_moves:
if move.product_id.id == product_id and move.state not in ['done', 'cancel']:
qty_on_link = min(move.remaining_qty, qty_to_assign)
link_obj.create(cr, uid, {'move_id': move.id, 'operation_id': op.id, 'qty': qty_on_link}, context=context)
@ -3833,7 +3902,7 @@ class stock_picking_type(osv.osv):
return res and res[0] or False
_columns = {
'name': fields.char('Name', translate=True, required=True),
'name': fields.char('Picking Type Name', translate=True, required=True),
'complete_name': fields.function(_get_name, type='char', string='Name'),
'auto_force_assign': fields.boolean('Automatic Availability', help='This picking type does\'t need to check for the availability in source location.'),
'color': fields.integer('Color'),

View File

@ -126,7 +126,7 @@
<page string="Inventory Details" >
<button name="set_checked_qty" states="confirm" string="⇒ Set quantities to 0" type="object" class="oe_link oe_right" groups="stock.group_stock_user"/>
<field name="line_ids" string="Inventory Details" context="{'default_location_id': location_id, 'default_product_id': product_id, 'default_prod_lot_id': lot_id, 'default_package_id': package_id, 'default_partner_id': partner_id}">
<tree string="Inventory Details" editable="bottom" colors="blue: product_qty != th_qty">
<tree string="Inventory Details" editable="bottom" colors="blue: product_qty != th_qty; red: th_qty &lt; 0">
<field context="{'location':location_id, 'uom':product_uom_id, 'to_date':parent.date}" name="product_id" on_change="on_change_product_id(location_id,product_id,product_uom_id,partner_id,prod_lot_id,package_id)" domain="[('type','=','product')]"/>
<field name="product_uom_id" groups="product.group_uom" on_change="on_change_product_id(location_id,product_id,product_uom_id,partner_id,prod_lot_id,package_id)"/>
<field domain="[('usage','=','internal')]" name="location_id" groups="stock.group_locations" on_change="on_change_product_id(location_id,product_id,product_uom_id,partner_id,prod_lot_id,package_id)"/>
@ -585,16 +585,6 @@
<menuitem action="action_location_form" id="menu_action_location_form" groups="stock.group_locations"
parent="menu_stock_configuration" sequence="5"/>
<record id="view_location_tree" model="ir.ui.view">
<field name="name">stock.location.tree</field>
<field name="model">stock.location</field>
<field name="field_parent">child_ids</field>
<field name="arch" type="xml">
<tree toolbar="1" string="Locations" >
<field name="name"/>
</tree>
</field>
</record>
<record model="ir.actions.act_window" id="action_product_location_tree">
<field name="context">{'product_id': active_id}</field>
<field name="name">Stock by Location</field>
@ -605,29 +595,6 @@
<field name="name">Moves</field>
<field name="res_model">stock.move</field>
</record>
<record id="action_location_tree" model="ir.actions.act_window">
<field name="name">Inventory by Location</field>
<field name="res_model">stock.location</field>
<field name="type">ir.actions.act_window</field>
<field name="domain">[('location_id','=',False)]</field>
<field name="view_type">tree</field>
<field name="view_id" ref="view_location_tree"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to add a location.
</p><p>
This is the structure of your company's warehouses and
locations. You can click on a location to get the list of the
products and their stock level in this particular location and
all its children.
</p>
</field>
</record>
<menuitem action="action_location_tree"
id="menu_action_location_tree"
parent="menu_stock_inventory_control"
groups="stock.group_locations"
sequence="20"/>
<record id="view_warehouse" model="ir.ui.view">
<field name="name">stock.warehouse</field>
@ -816,11 +783,10 @@
<field name="note" placeholder="Add an internal note..." class="oe_inline"/>
</page>
<page string="Additional Info">
<group>
<group string="General Informations">
<group>
<field name="move_type"/>
<field name="picking_type_id"/>
<field name="priority"/>
<field name="picking_type_code" invisible="1"/>
<field name="quant_reserved_exist" invisible="1"/>
</group>
@ -828,6 +794,7 @@
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
<field name="date_done" groups="base.group_no_one"/>
<field name="group_id"/>
<field name="priority"/>
</group>
</group>
</page>
@ -1016,6 +983,18 @@
</field>
</record>
<record id="view_move_graph" model="ir.ui.view">
<field name="name">stock.move.graph</field>
<field name="model">stock.move</field>
<field name="arch" type="xml">
<graph string="Stock Moves Analysis" type="pivot">
<field name="product_id" type="row"/>
<field name="location_dest_id" groups="stock.group_locations" type="row"/>
<field name="product_uom_qty" type="measure"/>
</graph>
</field>
</record>
<record id="view_move_tree" model="ir.ui.view">
<field name="name">stock.move.tree</field>
<field name="model">stock.move</field>
@ -1186,6 +1165,9 @@
<field name="location_id" domain="[('usage','&lt;&gt;','view')]"/>
<field name="location_dest_id" domain="[('usage','=','internal')]"/>
</group>
<group name="quants_grp" string="Reserved Quants" colspan="4" groups="base.group_no_one">
<field name="reserved_quant_ids"/>
</group>
</group>
</form>
</field>
@ -1254,6 +1236,13 @@
<field name="act_window_id" ref="action_move_form2"/>
</record>
<record model="ir.actions.act_window.view" id="action_stock_move_graph_all">
<field name="sequence" eval="3"/>
<field name="view_mode">graph</field>
<field name="view_id" ref="view_move_graph"/>
<field name="act_window_id" ref="action_move_form2"/>
</record>
<menuitem action="action_move_form2" id="menu_action_move_form2" parent="menu_traceability" sequence="3" groups="stock.group_locations"/>
<record id="action_stock_stock_ui" model="ir.actions.client">
@ -2069,9 +2058,6 @@
<tree string="Routes">
<field name="sequence" widget="handle" />
<field name="name"/>
<field name="product_selectable"/>
<field name="product_categ_selectable"/>
<field name="warehouse_selectable"/>
</tree>
</field>
</record>
@ -2090,10 +2076,10 @@
<group>
<group>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
<field name="sequence" string="Sequence" groups="base.group_no_one"/>
<field name="active" groups="stock.group_adv_location" />
</group>
<group>
<field name="active" groups="stock.group_adv_location" />
<field name="sequence" string="Sequence" groups="base.group_no_one"/>
</group>
</group>
<separator string="Applicable On"/>

View File

@ -7,8 +7,7 @@
<field name="name">Scrap Move</field>
<field name="model">stock.move.scrap</field>
<field name="arch" type="xml">
<form string="Scrap Move" version="7.0">
<separator string="Scrap Products"/>
<form string="Scrap Products" version="7.0">
<group>
<field name="product_id" readonly="1"/>
<label for="product_qty"/>
@ -22,7 +21,7 @@
domain="[('usage','!=','view'),('scrap_location','=',True)]" groups="stock.group_locations"/>
</group>
<footer>
<button name="move_scrap" string="Ok" type="object" class="oe_highlight" />
<button name="move_scrap" string="Scrap" type="object" class="oe_highlight" />
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>

View File

@ -8,10 +8,12 @@ class wizard_valuation_history(osv.osv_memory):
_name = 'wizard.valuation.history'
_description = 'Wizard that opens the stock valuation history table'
_columns = {
'choose_date': fields.boolean('Choose a Particular Date'),
'date': fields.datetime('Date', required=True),
}
_defaults = {
'choose_date': False,
'date': fields.datetime.now,
}
@ -27,7 +29,7 @@ class wizard_valuation_history(osv.osv_memory):
'domain': "[('date', '<=', '" + data['date'] + "')]",
'name': _('Stock Value At Date'),
'view_type': 'form',
'view_mode': 'tree',
'view_mode': 'tree, graph',
'res_model': 'stock.history',
'type': 'ir.actions.act_window',
'context': ctx,
@ -68,7 +70,7 @@ class stock_history(osv.osv):
'location_id': fields.many2one('stock.location', 'Location', required=True),
'product_id': fields.many2one('product.product', 'Product', required=True),
'product_categ_id': fields.many2one('product.category', 'Product Category', required=True),
'quantity': fields.integer('Quantity'),
'quantity': fields.integer('Product Quantity'),
'date': fields.datetime('Operation Date'),
'price_unit_on_quant': fields.float('Value'),
'cost_method': fields.char('Cost Method'),

View File

@ -7,14 +7,18 @@
<field name="model">wizard.valuation.history</field>
<field name="arch" type="xml">
<form string="Choose your date" version="7.0">
<p class="oe_gray">
<p class="oe_gray" attrs="{'invisible': [('choose_date', '=', False)]}">
Choose the date for wich you want to get the stock valuation of your products.
This will filter the stock operation that weren't done at the selected date, to retreive the quantity
you had, and gives you the inventory value according to the standard price used at that time.
</p>
<p class="oe_gray" attrs="{'invisible': [('choose_date', '=', True)]}">
Retrieve the stock valuation of your products at current day
</p>
<group>
<field name="date"/>
<field name="choose_date"/>
<field name="date" attrs="{'invisible': [('choose_date', '=', False)]}"/>
</group>
<footer>
<button name="open_table" string="Retrieve the Inventory Value" type="object" class="oe_highlight" />
@ -49,6 +53,18 @@
</tree>
</field>
</record>
<record id="view_stock_history_report_graph" model="ir.ui.view">
<field name="name">stock.history.value.graph</field>
<field name="model">stock.history</field>
<field name="arch" type="xml">
<graph string="Stock Value At Date" type="pivot">
<field name="product_id" type="row"/>
<field name="inventory_value" type="measure"/>
<field name="quantity" type="measure"/>
<field name="location_id" type="row"/>
</graph>
</field>
</record>
<record id="view_stock_history_report_search" model="ir.ui.view">
<field name="name">stock.history.report.search</field>
<field name="model">stock.history</field>
@ -67,6 +83,21 @@
</search>
</field>
</record>
<record id="action_history_tree" model="ir.actions.act_window">
<field name="name">Current Inventory Valuation</field>
<field name="res_model">stock.history</field>
<field name="type">ir.actions.act_window</field>
<field name="domain">[('date','&lt;=',time.strftime('%Y-%m-%d %H:%M:%S'))]</field>
<field name="view_type">form</field>
<field name="view_mode">tree,graph</field>
<field name="view_id" ref="view_stock_history_report_tree"/>
</record>
<menuitem action="action_history_tree"
id="menu_action_history_tree"
parent="stock.menu_stock_inventory_control"
groups="stock.group_locations"
sequence="20"/>
</data>
</openerp>

View File

@ -6,13 +6,10 @@ class stock_picking_wave(osv.osv):
_name = "stock.picking.wave"
_order = "name desc"
_columns = {
'name': fields.char('name', required=True, help='Name of the picking wave'),
'name': fields.char('Picking Wave Name', required=True, help='Name of the picking wave'),
'user_id': fields.many2one('res.users', 'Responsible', help='Person responsible for this wave'),
'time': fields.float('Time', help='Time it will take to perform the wave'),
'picking_ids': fields.one2many('stock.picking', 'wave_id', 'Pickings', help='List of picking associated to this wave'),
'capacity': fields.float('Capacity', help='The capacity of the transport used to get the goods'),
'capacity_uom': fields.many2one('product.uom', 'Unit of Measure', help='The Unity Of Measure of the transport capacity'),
'state': fields.selection([('draft', 'Draft'), ('in_progress', 'Running'), ('done', 'Done'), ('cancel', 'Cancelled')], required=True),
'state': fields.selection([('draft', 'Draft'), ('in_progress', 'Running'), ('done', 'Done'), ('cancel', 'Cancelled')], string="State", required=True),
}
_defaults = {

View File

@ -6,15 +6,11 @@
-->
<record id="stock_picking_wave_dry_1" model="stock.picking.wave">
<field name="name">Picking Dry for John</field>
<field name="capacity">15</field>
<field name="capacity_uom" ref="product.product_uom_ton" />
<field name="state">in_progress</field>
</record>
<record id="stock_picking_wave_freeze_1" model="stock.picking.wave">
<field name="name">Picking Freeze for Mickael</field>
<field name="capacity">600</field>
<field name="capacity_uom" ref="product.product_uom_kgm" />
<field name="state">in_progress</field>
</record>

View File

@ -21,17 +21,7 @@
</h1>
</div>
<group>
<group>
<field name="user_id"/>
</group>
<group>
<label for="capacity"/>
<div>
<field name="capacity" class="oe_inline"/>
<field name="capacity_uom" class="oe_inline"/>
</div>
<field name="time" widget="float_time"/>
</group>
<field name="user_id"/>
</group>
<separator string="Pickings"/>
<field name="picking_ids" widget="many2many" domain="[('state', 'not in', ('done', 'cancel'))]">
@ -60,9 +50,6 @@
<tree string="Stock Picking Waves" colors="black:state in ('in_progress','done');grey: state=='cancel'">
<field name="name"/>
<field name="user_id"/>
<field name="capacity"/>
<field name="capacity_uom"/>
<field name="time" widget="float_time"/>
<field name="state"/>
</tree>
</field>

View File

@ -378,20 +378,25 @@ class Website(openerp.addons.web.controllers.main.Home):
#------------------------------------------------------
# Server actions
#------------------------------------------------------
@http.route(['/website/action/<id_or_xml_id>'], type='http', auth="public", website=True)
def actions_server(self, id_or_xml_id, **post):
@http.route('/website/action/<path_or_xml_id_or_id>', type='http', auth="public", website=True)
def actions_server(self, path_or_xml_id_or_id, **post):
cr, uid, context = request.cr, request.uid, request.context
res, action_id, action = None, None, None
ServerActions = request.registry['ir.actions.server']
# find the action_id, either an int, an int into a basestring, or an xml_id
if isinstance(id_or_xml_id, basestring) and '.' in id_or_xml_id:
action_id = request.registry['ir.model.data'].xmlid_to_res_id(request.cr, request.uid, id_or_xml_id, raise_if_not_found=False)
else:
# find the action_id: either an xml_id, the path, or an ID
if isinstance(path_or_xml_id_or_id, basestring) and '.' in path_or_xml_id_or_id:
action_id = request.registry['ir.model.data'].xmlid_to_res_id(request.cr, request.uid, path_or_xml_id_or_id, raise_if_not_found=False)
if not action_id:
action_ids = ServerActions.search(cr, uid, [('website_path', '=', path_or_xml_id_or_id), ('website_published', '=', True)], context=context)
action_id = action_ids and action_ids[0] or None
if not action_id:
try:
action_id = int(id_or_xml_id)
action_id = int(path_or_xml_id_or_id)
except ValueError:
pass
# check it effectively exists
if action_id:
action_ids = ServerActions.exists(cr, uid, [action_id], context=context)

View File

@ -43,6 +43,7 @@
<field name="name">Website Partner Post and Thanks Demo</field>
<field name="condition">True</field>
<field name="website_published" eval="True"/>
<field name="website_path">partner_thanks</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="code">
partner_id, partner = False, None
@ -118,6 +119,7 @@ response = request.website.render("website.template_partner_post", values)
<field name="name">Website Partners Comment Form</field>
<field name="condition">True</field>
<field name="website_published" eval="True"/>
<field name="website_path">partner_comment</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="code">
partner_ids = pool['res.partner'].search(cr, uid, [('customer', '=', True)], context=context)

View File

@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
import urlparse
from openerp.addons.web.http import request
from openerp.osv import fields, osv
@ -9,15 +11,43 @@ class actions_server(osv.Model):
_name = 'ir.actions.server'
_inherit = ['ir.actions.server']
def _compute_website_url(self, cr, uid, id, website_path, xml_id, context=None):
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url', context=context)
link = website_path or xml_id or (id and '%d' % id) or ''
if base_url and link:
path = '%s/%s' % ('/website/action', link)
return '%s' % urlparse.urljoin(base_url, path)
return ''
def _get_website_url(self, cr, uid, ids, name, args, context=None):
res = dict.fromkeys(ids, False)
for action in self.browse(cr, uid, ids, context=context):
if action.state == 'code' and action.website_published:
res[action.id] = self._compute_website_url(cr, uid, action.id, action.website_path, action.xml_id, context=context)
return res
_columns = {
'xml_id': fields.function(
osv.osv.get_xml_id, type='char', string="External ID",
help="ID of the action if defined in a XML file"),
'website_path': fields.char('Website Path'),
'website_url': fields.function(
_get_website_url, type='char', string='Website URL',
help='The full URL to access the server action through the website.'),
'website_published': fields.boolean(
'Available on the Website',
help='A code server action can be executed from the website, using a dedicated'
'controller. The address is <base>/website/action/<id_or_xml_id>.'
'controller. The address is <base>/website/action/<website_path>.'
'Set this field as True to allow users to run this action. If it'
'set to is False the action cannot be run through the website.'),
}
def on_change_website_path(self, cr, uid, ids, website_path, xml_id, context=None):
values = {
'website_url': self._compute_website_url(cr, uid, ids[0], website_path, xml_id, context=context)
}
return {'value': values}
def _get_eval_context(self, cr, uid, action, context=None):
""" Override to add the request object in eval_context. """
eval_context = super(actions_server, self)._get_eval_context(cr, uid, action, context=context)

View File

@ -399,7 +399,7 @@ div.carousel[data-snippet-id="slider"] .carousel-indicators .active {
}
.oe_green {
background-color: #51d466;
background-color: #169c78;
color: white;
}
.oe_green .text-muted {
@ -407,7 +407,7 @@ div.carousel[data-snippet-id="slider"] .carousel-indicators .active {
}
.oe_blue_light {
background-color: #4791d2;
background-color: #41b6ab;
color: white;
}
.oe_blue_light .text-muted {
@ -420,7 +420,7 @@ div.carousel[data-snippet-id="slider"] .carousel-indicators .active {
}
.oe_orange {
background-color: #e67e22;
background-color: #f05442;
color: white;
}
.oe_orange .text-muted {
@ -436,7 +436,7 @@ div.carousel[data-snippet-id="slider"] .carousel-indicators .active {
}
.oe_red {
background-color: #f75353;
background-color: #9c1b31;
color: white;
}
.oe_red .text-muted {

View File

@ -328,13 +328,13 @@ div.carousel[data-snippet-id="slider"]
color: white
.oe_green
background-color: #51d466
background-color: #169C78
color: white
.text-muted
color: #ddd
.oe_blue_light
background-color: #4791d2
background-color: #41b6ab
color: white
.text-muted
color: #ddd
@ -344,7 +344,7 @@ div.carousel[data-snippet-id="slider"]
color: white
.oe_orange
background-color: #e67e22
background-color: #f05442
color: white
.text-muted
color: #ddd
@ -356,7 +356,7 @@ div.carousel[data-snippet-id="slider"]
color: #ddd
.oe_red
background-color: #f75353
background-color: #9C1b31
color: white
.text-muted
color: #ddd

Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 825 KiB

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 584 KiB

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 732 KiB

After

Width:  |  Height:  |  Size: 166 KiB

Some files were not shown because too many files have changed in this diff Show More