[MERGE] Convert remaining yml files and remove yml import engine
This commit is contained in:
@@ -1,70 +0,0 @@
|
||||
-
|
||||
Create demo bank statement
|
||||
-
|
||||
!python {model: ir.model.data, id: False} : |
|
||||
default_company = self.env['res.company']._company_default_get('account.journal')
|
||||
journal = self.env['account.journal'].search([('type', '=', 'bank'), ('company_id', '=', default_company.id)], limit=1)
|
||||
import time
|
||||
if journal:
|
||||
vals = {
|
||||
'name': 'demo_bank_statement_1',
|
||||
'journal_id': journal.id,
|
||||
'date': time.strftime('%Y')+'-01-01',
|
||||
'name': "BNK/2014/001",
|
||||
'balance_end_real': '8998.2',
|
||||
'balance_start': 5103.0
|
||||
}
|
||||
self._update('account.bank.statement', 'account', vals, 'demo_bank_statement_1')
|
||||
vals = {
|
||||
'ref': '',
|
||||
'statement_id': ref('account.demo_bank_statement_1'),
|
||||
'sequence': 1,
|
||||
'name': "SAJ/2014/002 and SAJ/2014/003",
|
||||
'journal_id': journal.id,
|
||||
'amount': 1175.0,
|
||||
'date': time.strftime('%Y')+'-01-01',
|
||||
'partner_id': ref('base.res_partner_2'),
|
||||
}
|
||||
self._update('account.bank.statement.line', 'account', vals, 'demo_bank_statement_line_1')
|
||||
vals = {
|
||||
'ref': '',
|
||||
'statement_id': ref('account.demo_bank_statement_1'),
|
||||
'sequence': 2,
|
||||
'name': "Bank fees",
|
||||
'journal_id': journal.id,
|
||||
'amount': -32.58,
|
||||
'date': time.strftime('%Y')+'-01-01',
|
||||
}
|
||||
self._update('account.bank.statement.line', 'account', vals, 'demo_bank_statement_line_2')
|
||||
vals = {
|
||||
'ref': '',
|
||||
'statement_id': ref('account.demo_bank_statement_1'),
|
||||
'sequence': 3,
|
||||
'name': "Prepayment",
|
||||
'journal_id': journal.id,
|
||||
'amount': 650.0,
|
||||
'date': time.strftime('%Y')+'-01-01',
|
||||
'partner_id': ref('base.res_partner_12'),
|
||||
}
|
||||
self._update('account.bank.statement.line', 'account', vals, 'demo_bank_statement_line_3')
|
||||
vals = {
|
||||
'ref': '',
|
||||
'statement_id': ref('account.demo_bank_statement_1'),
|
||||
'sequence': 4,
|
||||
'name': "First 2000 € of SAJ/2014/001",
|
||||
'journal_id': journal.id,
|
||||
'amount': 2000.0,
|
||||
'date': time.strftime('%Y')+'-01-01',
|
||||
'partner_id': ref('base.res_partner_12'),
|
||||
}
|
||||
self._update('account.bank.statement.line', 'account', vals, 'demo_bank_statement_line_4')
|
||||
vals = {
|
||||
'ref': '',
|
||||
'statement_id': ref('account.demo_bank_statement_1'),
|
||||
'sequence': 5,
|
||||
'name': "Last Year Interests",
|
||||
'journal_id': journal.id,
|
||||
'amount': 102.78,
|
||||
'date': time.strftime('%Y')+'-01-01',
|
||||
}
|
||||
self._update('account.bank.statement.line', 'account', vals, 'demo_bank_statement_line_5')
|
||||
@@ -1,185 +0,0 @@
|
||||
-
|
||||
Set the context for customer invoices because the 'type' field is readonly in invoice form views, so it value won't be considered by the yaml import
|
||||
-
|
||||
!record {model: account.invoice, id: demo_invoice_1}:
|
||||
partner_id: base.res_partner_12
|
||||
user_id: base.user_demo
|
||||
reference_type: none
|
||||
payment_term_id: account.account_payment_term
|
||||
type: 'out_invoice'
|
||||
date_invoice: !eval time.strftime('%Y-%m')+'-01'
|
||||
invoice_line_ids:
|
||||
- product_id: product.consu_delivery_02
|
||||
price_unit: 642.0
|
||||
quantity: 5
|
||||
- product_id: product.consu_delivery_03
|
||||
price_unit: 280.0
|
||||
quantity: 5.0
|
||||
-
|
||||
!python {model: account.invoice, id: demo_invoice_1}:
|
||||
self.action_invoice_open()
|
||||
-
|
||||
!record {model: account.invoice, id: demo_invoice_2}:
|
||||
partner_id: base.res_partner_2
|
||||
type: 'out_invoice'
|
||||
date_invoice: !eval time.strftime('%Y-%m')+'-08'
|
||||
invoice_line_ids:
|
||||
- product_id: product.consu_delivery_03
|
||||
price_unit: 50.0
|
||||
quantity: 3
|
||||
- product_id: product.consu_delivery_01
|
||||
price_unit: 25
|
||||
quantity: 20
|
||||
-
|
||||
!python {model: account.invoice, id: demo_invoice_2}:
|
||||
self.action_invoice_open()
|
||||
-
|
||||
!record {model: account.invoice, id: demo_invoice_3}:
|
||||
partner_id: base.res_partner_2
|
||||
type: 'out_invoice'
|
||||
date_invoice: !eval time.strftime('%Y-%m')+'-08'
|
||||
invoice_line_ids:
|
||||
- product_id: product.consu_delivery_01
|
||||
price_unit: 90.0
|
||||
quantity: 5
|
||||
- product_id: product.consu_delivery_03
|
||||
price_unit: 15.0
|
||||
quantity: 5.0
|
||||
-
|
||||
!python {model: account.invoice, id: demo_invoice_3}:
|
||||
self.action_invoice_open()
|
||||
-
|
||||
Create an invoice that will generate a followup
|
||||
-
|
||||
!record {model: account.invoice, id: demo_invoice_followup}:
|
||||
partner_id: base.res_partner_2
|
||||
user_id: base.user_demo
|
||||
reference_type: none
|
||||
payment_term_id: account.account_payment_term_immediate
|
||||
type: 'out_invoice'
|
||||
date_invoice: !eval (datetime.today() + timedelta(days=-15)).strftime('%Y-%m-%d')
|
||||
invoice_line_ids:
|
||||
- product_id: product.consu_delivery_02
|
||||
price_unit: 642.0
|
||||
quantity: 5
|
||||
- product_id: product.consu_delivery_03
|
||||
price_unit: 280.0
|
||||
quantity: 5.0
|
||||
-
|
||||
!python {model: account.invoice, id: demo_invoice_followup}:
|
||||
self.action_invoice_open()
|
||||
-
|
||||
Set the context for supplier bills because the 'type' field is readonly in invoice form views, so it value won't be considered by the yaml import
|
||||
-
|
||||
!context
|
||||
type: 'in_invoice'
|
||||
-
|
||||
!record {model: account.invoice, id: demo_invoice_0}:
|
||||
partner_id: base.res_partner_12
|
||||
user_id: base.user_demo
|
||||
reference_type: none
|
||||
payment_term_id: account.account_payment_term
|
||||
type: 'in_invoice'
|
||||
date_invoice: !eval time.strftime('%Y-%m')+'-01'
|
||||
invoice_line_ids:
|
||||
- price_unit: 10.0
|
||||
quantity: 1.0
|
||||
product_id: product.product_delivery_01
|
||||
uom_id: product.product_uom_unit
|
||||
- price_unit: 4.0
|
||||
quantity: 1.0
|
||||
product_id: product.product_order_01
|
||||
uom_id: product.product_uom_unit
|
||||
-
|
||||
!record {model: account.invoice, id: demo_invoice_january_wages}:
|
||||
partner_id: base.res_partner_12
|
||||
type: 'in_invoice'
|
||||
date_invoice: !eval time.strftime('%Y-%m')+'-01'
|
||||
-
|
||||
!python {model: ir.model.data, id: False} : |
|
||||
line_vals = {
|
||||
'invoice_id': ref('demo_invoice_january_wages'),
|
||||
'name': "CEO wages",
|
||||
'price_unit': 10000,
|
||||
}
|
||||
default_company = self.env['res.company']._company_default_get('account.account')
|
||||
account = self.env['account.account'].search([('tag_ids', 'in', [ref('account.demo_ceo_wages_account')]), ('company_id', '=', default_company.id)], limit=1)
|
||||
if not account:
|
||||
account = self.env['account.account'].search([('user_type_id', '=', ref('account.data_account_type_expenses')), ('company_id', '=', default_company.id)], limit=1)
|
||||
if account:
|
||||
line_vals['account_id'] = account.id
|
||||
self._update('account.invoice.line', 'account', line_vals, 'ceo_wages_line')
|
||||
self.env['account.invoice'].browse(ref('demo_invoice_january_wages')).action_invoice_open()
|
||||
-
|
||||
!record {model: account.invoice, id: demo_invoice_equipment_purchase}:
|
||||
partner_id: base.res_partner_1
|
||||
type: 'in_invoice'
|
||||
date_invoice: !eval time.strftime('%Y-%m')+'-15'
|
||||
-
|
||||
!python {model: ir.model.data, id: False} : |
|
||||
line_vals = {
|
||||
'invoice_id': ref('demo_invoice_equipment_purchase'),
|
||||
'name': "Coffee Machine with huge 'employee\'s performances boosting perk'",
|
||||
'price_unit': 4999.99,
|
||||
}
|
||||
account_default_company = self.env['res.company']._company_default_get('account.account')
|
||||
account_id = self.env['account.account'].search([('tag_ids', 'in', [ref('account.demo_coffee_machine_account')]), ('company_id', '=', account_default_company.id)], limit=1).id
|
||||
if not account_id:
|
||||
account_id = self.env['account.account'].search([('user_type_id', '=', ref('account.data_account_type_expenses')), ('company_id', '=', account_default_company.id)], limit=1).id
|
||||
if account_id:
|
||||
line_vals['account_id'] = account_id
|
||||
tax_default_company = self.env['res.company']._company_default_get('account.tax')
|
||||
tax_id = self.env['account.tax'].search([('type_tax_use', '=', 'purchase'), ('amount', '>', 0), ('company_id', '=', tax_default_company.id)], limit=1).id
|
||||
if tax_id:
|
||||
line_vals['invoice_line_tax_ids'] = [(6, 0, [tax_id])]
|
||||
self._update('account.invoice.line', 'account', line_vals, 'coffee_machine_line')
|
||||
self.env['account.invoice'].browse(ref('demo_invoice_equipment_purchase'))._onchange_invoice_line_ids()
|
||||
self.env['account.invoice'].browse(ref('demo_invoice_equipment_purchase')).action_invoice_open()
|
||||
|
||||
-
|
||||
!python {model: account.bank.statement.line, id: False} : |
|
||||
default_company = self.env['res.company']._company_default_get('account.account')
|
||||
account = self.env['account.account'].search([
|
||||
('user_type_id', '=', ref('account.data_account_type_revenue')),
|
||||
('tag_ids', 'in', [ref('account.account_tag_financing')]),
|
||||
('company_id', '=', default_company.id)], limit=1)
|
||||
line = self.env.ref('account.demo_bank_statement_line_5')
|
||||
if not line.journal_entry_ids and account:
|
||||
line.write({'account_id': account.id})
|
||||
line.fast_counterpart_creation()
|
||||
-
|
||||
!python {model: ir.model.data, id: False} : |
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
default_company = self.env['res.company']._company_default_get('account.journal')
|
||||
vals = {
|
||||
'journal_id': self.env['account.journal'].search([('type', '=', 'general'), ('company_id', '=', default_company.id)], limit=1).id,
|
||||
'date': datetime.now().replace(day=1,month=1) - relativedelta(days=1),
|
||||
'ref': 'Company Creation',
|
||||
}
|
||||
self._update('account.move', 'account', vals, 'demo_opening_move')
|
||||
-
|
||||
!python {model: ir.model.data, id: False} : |
|
||||
journal_default_company = self.env['res.company']._company_default_get('account.journal')
|
||||
bank_journal = self.env['account.journal'].search([('type', '=', 'bank'), ('company_id', '=', journal_default_company.id)], limit=1)
|
||||
if bank_journal:
|
||||
bank_account_id = bank_journal.default_debit_account_id.id
|
||||
account_default_company = self.env['res.company']._company_default_get('account.account')
|
||||
fixed_asset_account_id = self.env['account.account'].search([('user_type_id', 'in', (ref('account.data_account_type_fixed_assets'),ref('account.data_account_type_current_assets'))), ('company_id', '=', account_default_company.id)], limit=1).id
|
||||
equity_account_id = self.env['account.account'].search([('user_type_id', '=', ref('account.data_account_type_equity')), ('tag_ids', 'in', [ref('account.demo_capital_account')]), ('company_id', '=', account_default_company.id)], limit=1).id
|
||||
if bank_account_id and fixed_asset_account_id and equity_account_id:
|
||||
self.with_context({'check_move_validity': False})._update('account.move.line', 'account', {
|
||||
'move_id': ref('account.demo_opening_move'),
|
||||
'name': 'Opening Entry',
|
||||
'debit': 5103,
|
||||
'account_id': bank_account_id}, 'opening_line_1')
|
||||
self.with_context({'check_move_validity': False})._update('account.move.line', 'account', {
|
||||
'move_id': ref('account.demo_opening_move'),
|
||||
'name': 'Opening Entry',
|
||||
'debit': 13447,
|
||||
'account_id': fixed_asset_account_id}, 'opening_line_2')
|
||||
self.with_context({'check_move_validity': False})._update('account.move.line', 'account', {
|
||||
'move_id': ref('account.demo_opening_move'),
|
||||
'name': 'Opening Entry',
|
||||
'credit': 18550,
|
||||
'account_id': equity_account_id}, 'opening_line_3')
|
||||
@@ -14,9 +14,6 @@ Keeps track of depreciations, and creates corresponding journal entries.
|
||||
'website': 'https://www.odoo.com/page/accounting',
|
||||
'category': 'Accounting',
|
||||
'sequence': 32,
|
||||
'demo': [
|
||||
'data/account_asset_demo.yml',
|
||||
],
|
||||
'data': [
|
||||
'security/account_asset_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
-
|
||||
Creating demo data that require looking for accounts
|
||||
-
|
||||
!python {model: ir.model.data, id: False} : |
|
||||
company = self.env.user.company_id
|
||||
journal_id = self.env['account.journal'].search([('type', '=', 'general'), ('id', '!=', company.currency_exchange_journal_id.id)], limit=1).id
|
||||
expense_account_id = self.env['account.account'].search([('user_type_id', '=', ref('account.data_account_type_expenses'))], limit=1).id
|
||||
xfa_account_id = self.env['account.account'].search([('user_type_id', '=', ref('account.data_account_type_fixed_assets'))], limit=1).id
|
||||
if not xfa_account_id:
|
||||
xfa_account_id = self.env['account.account'].search([('user_type_id', '=', ref('account.data_account_type_current_assets'))], limit=1).id
|
||||
from datetime import datetime, timedelta
|
||||
import time
|
||||
if xfa_account_id and expense_account_id and journal_id:
|
||||
vals = {
|
||||
'journal_id': journal_id,
|
||||
'name': 'Hardware - 3 Years',
|
||||
'method_number': 3,
|
||||
'account_asset_id': xfa_account_id,
|
||||
'account_depreciation_id': xfa_account_id,
|
||||
'account_depreciation_expense_id': expense_account_id,
|
||||
}
|
||||
self._update('account.asset.category', 'account_asset', vals, 'account_asset_category_fixedassets0')
|
||||
vals = {
|
||||
'journal_id': journal_id,
|
||||
'name': 'Cars - 5 Years',
|
||||
'method_number': 5,
|
||||
'account_asset_id': xfa_account_id,
|
||||
'account_depreciation_id': xfa_account_id,
|
||||
'account_depreciation_expense_id': expense_account_id,
|
||||
}
|
||||
self._update('account.asset.category', 'account_asset', vals, 'account_asset_category_fixedassets1')
|
||||
vals = {
|
||||
'salvage_value': 2000.0,
|
||||
'state': 'open',
|
||||
'method_period': 12,
|
||||
'method_number': 5,
|
||||
'name': "CEO's car",
|
||||
'value': 12000.0,
|
||||
'category_id': ref('account_asset_category_fixedassets0'),
|
||||
}
|
||||
self._update('account.asset.asset', 'account_asset', vals, 'account_asset_asset_vehicles0')
|
||||
vals = {
|
||||
'method_end': (datetime.now().replace(month=8, day=11) + timedelta(days=3*365)).strftime('%Y-%m-%d'),
|
||||
'salvage_value': 0.0,
|
||||
'method_time': 'end',
|
||||
'name': 'V6 Engine and 10 inches tires',
|
||||
'value': 2800.0,
|
||||
'category_id': ref('account_asset_category_fixedassets0'),
|
||||
}
|
||||
self._update('account.asset.asset', 'account_asset', vals, 'account_asset_asset_cab0')
|
||||
vals = {
|
||||
'prorata': 1,
|
||||
'salvage_value': 100000.0,
|
||||
'state': 'open',
|
||||
'method_period': 12,
|
||||
'method_number': 20,
|
||||
'date': time.strftime('%Y-01-01'),
|
||||
'name': 'Office',
|
||||
'value': 500000.0,
|
||||
'category_id': ref('account_asset_category_fixedassets0'),
|
||||
}
|
||||
self._update('account.asset.asset', 'account_asset', vals, 'account_asset_asset_office0')
|
||||
@@ -12,7 +12,6 @@ Allow users to login through OAuth2 Provider.
|
||||
'depends': ['base', 'web', 'base_setup', 'auth_signup'],
|
||||
'data': [
|
||||
'data/auth_oauth_data.xml',
|
||||
'data/auth_oauth_data.yml',
|
||||
'views/auth_oauth_views.xml',
|
||||
'views/res_users_views.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
|
||||
@@ -29,5 +29,13 @@
|
||||
<field name="css_class">fa fa-google-plus-square</field>
|
||||
<field name="body">Log in with Google</field>
|
||||
</record>
|
||||
|
||||
<!-- Use database uuid as client_id for OpenERP oauth provider -->
|
||||
<function model="auth.oauth.provider" name="write">
|
||||
<value eval="[ref('auth_oauth.provider_openerp')]"/>
|
||||
<value model="ir.config_parameter" eval="{
|
||||
'client_id': obj().env['ir.config_parameter'].get_param('database.uuid'),
|
||||
}"/>
|
||||
</function>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
-
|
||||
Use database uuid as client_id for OpenERP oauth provider
|
||||
-
|
||||
!python {model: ir.config_parameter, id: False}: |
|
||||
oauth = self.env['auth.oauth.provider']
|
||||
oauth.browse(ref('provider_openerp')).write({'client_id': self.get_param('database.uuid')})
|
||||
@@ -54,7 +54,7 @@ Wizards provided by this module:
|
||||
'data/menuitem_data.xml',
|
||||
],
|
||||
'demo': [
|
||||
'demo/l10n_be_demo.yml',
|
||||
'demo/l10n_be_demo.xml',
|
||||
],
|
||||
'website': 'https://www.odoo.com/page/accounting',
|
||||
'post_init_hook': 'load_translations',
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="l10n_be.a1000" model="account.account.template">
|
||||
<field name="tag_ids" eval="[(4, ref('account.demo_capital_account'))]"/>
|
||||
</record>
|
||||
<record id="l10n_be.a300" model="account.account.template">
|
||||
<field name="tag_ids" eval="[(4, ref('account.demo_stock_account'))]"/>
|
||||
</record>
|
||||
<record id="l10n_be.a7600" model="account.account.template">
|
||||
<field name="tag_ids" eval="[(4, ref('account.demo_sale_of_land_account'))]"/>
|
||||
</record>
|
||||
<record id="l10n_be.a6201" model="account.account.template">
|
||||
<field name="tag_ids" eval="[(4, ref('account.demo_ceo_wages_account'))]"/>
|
||||
</record>
|
||||
<record id="l10n_be.a24011" model="account.account.template">
|
||||
<field name="tag_ids" eval="[(4, ref('account.demo_coffee_machine_account'))]"/>
|
||||
</record>
|
||||
<function model="account.account" name="write">
|
||||
<value model="account.account" search="[('code', 'like', obj().env.ref('l10n_be.a1000').code)]"/>
|
||||
<value eval="{'tag_ids': [(4, ref('account.demo_capital_account'))]}"/>
|
||||
</function>
|
||||
<function model="account.account" name="write">
|
||||
<value model="account.account" search="[('code', 'like', obj().env.ref('l10n_be.a300').code)]"/>
|
||||
<value eval="{'tag_ids': [(4, ref('account.demo_stock_account'))]}"/>
|
||||
</function>
|
||||
<function model="account.account" name="write">
|
||||
<value model="account.account" search="[('code', 'like', obj().env.ref('l10n_be.a7600').code)]"/>
|
||||
<value eval="{'tag_ids': [(4, ref('account.demo_sale_of_land_account'))]}"/>
|
||||
</function>
|
||||
<function model="account.account" name="write">
|
||||
<value model="account.account" search="[('code', 'like', obj().env.ref('l10n_be.a6201').code)]"/>
|
||||
<value eval="{'tag_ids': [(4, ref('account.demo_ceo_wages_account'))]}"/>
|
||||
</function>
|
||||
<function model="account.account" name="write">
|
||||
<value model="account.account" search="[('code', 'like', obj().env.ref('l10n_be.a24011').code)]"/>
|
||||
<value eval="{'tag_ids': [(4, ref('account.demo_coffee_machine_account'))]}"/>
|
||||
</function>
|
||||
</odoo>
|
||||
@@ -1,18 +0,0 @@
|
||||
-
|
||||
Set the demo tags on account templates and on their respective accounts (already generated during the loading of data)
|
||||
-
|
||||
!python {model: account.account.template, id: False}: |
|
||||
mapping_list = [
|
||||
('a1000', 'account.demo_capital_account'),
|
||||
('a300', 'account.demo_stock_account'),
|
||||
('a7600', 'account.demo_sale_of_land_account'),
|
||||
('a6201', 'account.demo_ceo_wages_account'),
|
||||
('a24011', 'account.demo_coffee_machine_account'),
|
||||
]
|
||||
for xml_id, tag in mapping_list:
|
||||
account_template = self.browse(ref(xml_id))
|
||||
account_template.write({'tag_ids': [(4, ref(tag))]})
|
||||
accounts = self.env['account.account'].search([('code', 'like', account_template.code)])
|
||||
if accounts:
|
||||
accounts.write({'tag_ids': [(4,ref(tag))]})
|
||||
|
||||
@@ -20,8 +20,8 @@ Install some generic chart of accounts.
|
||||
'data/account_chart_template_data.xml',
|
||||
],
|
||||
'demo': [
|
||||
'../account/demo/account_bank_statement.yml',
|
||||
'../account/demo/account_invoice_demo.yml',
|
||||
'data/account_bank_statement_demo.xml',
|
||||
'data/account_invoice_demo.xml',
|
||||
],
|
||||
'website': 'https://www.odoo.com/page/accounting',
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="demo_bank_statement_1" model="account.bank.statement">
|
||||
<field name="journal_id" model="account.journal" search="[
|
||||
('type', '=', 'bank'),
|
||||
('company_id', '=', obj().env['res.company']._company_default_get('account.journal').id)]"/>
|
||||
<field name="date" eval="time.strftime('%Y')+'-01-01'"/>
|
||||
<field name="name">BNK/2014/001</field>
|
||||
<field name="balance_end_real">8998.2</field>
|
||||
<field name="balance_start">5103.0</field>
|
||||
</record>
|
||||
|
||||
<record id="demo_bank_statement_line_1" model="account.bank.statement.line">
|
||||
<field name="ref">''</field>
|
||||
<field name="statement_id" ref="l10n_generic_coa.demo_bank_statement_1"/>
|
||||
<field name="sequence">1</field>
|
||||
<field name="name">SAJ/2014/002 and SAJ/2014/003</field>
|
||||
<field name="journal_id" model="account.journal" search="[
|
||||
('type', '=', 'bank'),
|
||||
('company_id', '=', obj().env['res.company']._company_default_get('account.journal').id)]"/>
|
||||
<field name="amount">1175.0</field>
|
||||
<field name="date" eval="time.strftime('%Y')+'-01-01'"/>
|
||||
<field name="partner_id" ref="base.res_partner_2"/>
|
||||
</record>
|
||||
|
||||
<record id="demo_bank_statement_line_2" model="account.bank.statement.line">
|
||||
<field name="ref">''</field>
|
||||
<field name="statement_id" ref="l10n_generic_coa.demo_bank_statement_1"/>
|
||||
<field name="sequence">2</field>
|
||||
<field name="name">Bank fees</field>
|
||||
<field name="journal_id" model="account.journal" search="[
|
||||
('type', '=', 'bank'),
|
||||
('company_id', '=', obj().env['res.company']._company_default_get('account.journal').id)]"/>
|
||||
<field name="amount">-32.58</field>
|
||||
<field name="date" eval="time.strftime('%Y')+'-01-01'"/>
|
||||
</record>
|
||||
|
||||
<record id="demo_bank_statement_line_3" model="account.bank.statement.line">
|
||||
<field name="ref">''</field>
|
||||
<field name="statement_id" ref="l10n_generic_coa.demo_bank_statement_1"/>
|
||||
<field name="sequence">3</field>
|
||||
<field name="name">Prepayment</field>
|
||||
<field name="journal_id" model="account.journal" search="[
|
||||
('type', '=', 'bank'),
|
||||
('company_id', '=', obj().env['res.company']._company_default_get('account.journal').id)]"/>
|
||||
<field name="amount">650.0</field>
|
||||
<field name="date" eval="time.strftime('%Y')+'-01-01'"/>
|
||||
<field name="partner_id" ref="base.res_partner_12"/>
|
||||
</record>
|
||||
|
||||
<record id="demo_bank_statement_line_4" model="account.bank.statement.line">
|
||||
<field name="ref">''</field>
|
||||
<field name="statement_id" ref="l10n_generic_coa.demo_bank_statement_1"/>
|
||||
<field name="sequence">4</field>
|
||||
<field name="name">First 2000 € of SAJ/2014/001</field>
|
||||
<field name="journal_id" model="account.journal" search="[
|
||||
('type', '=', 'bank'),
|
||||
('company_id', '=', obj().env['res.company']._company_default_get('account.journal').id)]"/>
|
||||
<field name="amount">2000.0</field>
|
||||
<field name="date" eval="time.strftime('%Y')+'-01-01'"/>
|
||||
<field name="partner_id" ref="base.res_partner_12"/>
|
||||
</record>
|
||||
|
||||
<record id="demo_bank_statement_line_5" model="account.bank.statement.line">
|
||||
<field name="ref">''</field>
|
||||
<field name="statement_id" ref="l10n_generic_coa.demo_bank_statement_1"/>
|
||||
<field name="sequence">5</field>
|
||||
<field name="name">Last Year Interests</field>
|
||||
<field name="journal_id" model="account.journal" search="[
|
||||
('type', '=', 'bank'),
|
||||
('company_id', '=', obj().env['res.company']._company_default_get('account.journal').id)]"/>
|
||||
<field name="amount">102.78</field>
|
||||
<field name="date" eval="time.strftime('%Y')+'-01-01'"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,187 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="demo_invoice_1" model="account.invoice">
|
||||
<field name="partner_id" ref="base.res_partner_12"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="reference_type">none</field>
|
||||
<field name="payment_term_id" ref="account.account_payment_term"/>
|
||||
<field name="type">out_invoice</field>
|
||||
<field name="date_invoice" eval="time.strftime('%Y-%m')+'-01'"/>
|
||||
<field name="invoice_line_ids" model="account.invoice.line" eval="[(5, 0, 0),
|
||||
(0, 0, {
|
||||
'name': obj().env.ref('product.consu_delivery_02').partner_ref,
|
||||
'product_id': ref('product.consu_delivery_02'),
|
||||
'account_id': obj().env.ref('product.consu_delivery_02').categ_id.property_account_income_categ_id.id,
|
||||
'price_unit': 642.0,
|
||||
'quantity': 5}),
|
||||
(0, 0, {
|
||||
'name': obj().env.ref('product.consu_delivery_03').partner_ref,
|
||||
'product_id': ref('product.consu_delivery_03'),
|
||||
'account_id': obj().env.ref('product.consu_delivery_03').categ_id.property_account_income_categ_id.id,
|
||||
'price_unit': 280.0,
|
||||
'quantity': 5.0})]"/>
|
||||
</record>
|
||||
|
||||
<function model="account.invoice" name="action_invoice_open">
|
||||
<value eval="[ref('demo_invoice_1')]"/>
|
||||
</function>
|
||||
|
||||
<record id="demo_invoice_2" model="account.invoice">
|
||||
<field name="partner_id" ref="base.res_partner_2"/>
|
||||
<field name="type">out_invoice</field>
|
||||
<field name="date_invoice" eval="time.strftime('%Y-%m')+'-08'"/>
|
||||
<field name="invoice_line_ids" model="account.invoice.line" eval="[(5, 0, 0),
|
||||
(0, 0, {
|
||||
'name': obj().env.ref('product.consu_delivery_03').partner_ref,
|
||||
'product_id': ref('product.consu_delivery_03'),
|
||||
'account_id': obj().env.ref('product.consu_delivery_03').categ_id.property_account_income_categ_id.id,
|
||||
'price_unit': 50.0,
|
||||
'quantity': 3}),
|
||||
(0, 0, {
|
||||
'name': obj().env.ref('product.consu_delivery_01').partner_ref,
|
||||
'product_id': ref('product.consu_delivery_01'),
|
||||
'account_id': obj().env.ref('product.consu_delivery_01').categ_id.property_account_income_categ_id.id,
|
||||
'price_unit': 25.0,
|
||||
'quantity': 20.0})]"/>
|
||||
</record>
|
||||
|
||||
<function model="account.invoice" name="action_invoice_open">
|
||||
<value eval="[ref('demo_invoice_2')]"/>
|
||||
</function>
|
||||
|
||||
<record id="demo_invoice_3" model="account.invoice">
|
||||
<field name="partner_id" ref="base.res_partner_2"/>
|
||||
<field name="type">out_invoice</field>
|
||||
<field name="date_invoice" eval="time.strftime('%Y-%m')+'-08'"/>
|
||||
<field name="invoice_line_ids" model="account.invoice.line" eval="[(5, 0, 0),
|
||||
(0, 0, {
|
||||
'name': obj().env.ref('product.consu_delivery_01').partner_ref,
|
||||
'product_id': ref('product.consu_delivery_01'),
|
||||
'account_id': obj().env.ref('product.consu_delivery_01').categ_id.property_account_income_categ_id.id,
|
||||
'price_unit': 90.0,
|
||||
'quantity': 5}),
|
||||
(0, 0, {
|
||||
'name': obj().env.ref('product.consu_delivery_03').partner_ref,
|
||||
'product_id': ref('product.consu_delivery_03'),
|
||||
'account_id': obj().env.ref('product.consu_delivery_03').categ_id.property_account_income_categ_id.id,
|
||||
'price_unit': 15.0,
|
||||
'quantity': 5.0})]"/>
|
||||
</record>
|
||||
|
||||
<function model="account.invoice" name="action_invoice_open">
|
||||
<value eval="[ref('demo_invoice_3')]"/>
|
||||
</function>
|
||||
|
||||
<!-- Create an invoice that will generate a followup -->
|
||||
<record id="demo_invoice_followup" model="account.invoice">
|
||||
<field name="partner_id" ref="base.res_partner_2"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="reference_type">none</field>
|
||||
<field name="payment_term_id" ref="account.account_payment_term_immediate"/>
|
||||
<field name="type">out_invoice</field>
|
||||
<field name="date_invoice" eval="(datetime.today() + timedelta(days=-15)).strftime('%Y-%m-%d')"/>
|
||||
<field name="invoice_line_ids" model="account.invoice.line" eval="[(5, 0, 0),
|
||||
(0, 0, {
|
||||
'name': obj().env.ref('product.consu_delivery_02').partner_ref,
|
||||
'product_id': ref('product.consu_delivery_02'),
|
||||
'account_id': obj().env.ref('product.consu_delivery_02').categ_id.property_account_income_categ_id.id,
|
||||
'price_unit': 642.0,
|
||||
'quantity': 5}),
|
||||
(0, 0, {
|
||||
'name': obj().env.ref('product.consu_delivery_03').partner_ref,
|
||||
'product_id': ref('product.consu_delivery_03'),
|
||||
'account_id': obj().env.ref('product.consu_delivery_03').categ_id.property_account_income_categ_id.id,
|
||||
'price_unit': 280.0,
|
||||
'quantity': 5.0})]"/>
|
||||
</record>
|
||||
|
||||
<function model="account.invoice" name="action_invoice_open">
|
||||
<value eval="[ref('demo_invoice_followup')]"/>
|
||||
</function>
|
||||
|
||||
<record id="demo_invoice_0" model="account.invoice">
|
||||
<field name="partner_id" ref="base.res_partner_12"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="reference_type">none</field>
|
||||
<field name="payment_term_id" ref="account.account_payment_term"/>
|
||||
<field name="type">in_invoice</field>
|
||||
<field name="date_invoice" eval="time.strftime('%Y-%m')+'-01'"/>
|
||||
<field name="invoice_line_ids" model="account.invoice.line" eval="[(5, 0, 0),
|
||||
(0, 0, {
|
||||
'name': obj().env.ref('product.product_delivery_01').partner_ref,
|
||||
'product_id': ref('product.product_delivery_01'),
|
||||
'account_id': obj().env.ref('product.product_delivery_01').categ_id.property_account_income_categ_id.id,
|
||||
'price_unit': 10.0,
|
||||
'quantity': 1,
|
||||
'uom_id': ref('product.product_uom_unit')}),
|
||||
(0, 0, {
|
||||
'name': obj().env.ref('product.product_order_01').partner_ref,
|
||||
'product_id': ref('product.product_order_01'),
|
||||
'account_id': obj().env.ref('product.product_order_01').categ_id.property_account_income_categ_id.id,
|
||||
'price_unit': 4.0,
|
||||
'quantity': 1.0,
|
||||
'uom_id': ref('product.product_uom_unit')})]"/>
|
||||
</record>
|
||||
|
||||
<record id="demo_invoice_january_wages" model="account.invoice">
|
||||
<field name="partner_id" ref="base.res_partner_12"/>
|
||||
<field name="type">in_invoice</field>
|
||||
<field name="date_invoice" eval="time.strftime('%Y-%m')+'-01'"/>
|
||||
</record>
|
||||
|
||||
<record id="ceo_wages_line" model="account.invoice.line">
|
||||
<field name="invoice_id" ref="demo_invoice_january_wages"/>
|
||||
<field name="name">CEO wages</field>
|
||||
<field name="price_unit">10000</field>
|
||||
<field name="account_id" model="account.account"
|
||||
search="['|', ('tag_ids', '=', ref('account.demo_ceo_wages_account')),
|
||||
('user_type_id', '=', ref('account.data_account_type_expenses')),
|
||||
('company_id', '=', obj().env['res.company']._company_default_get('account.account').id)]"/>
|
||||
</record>
|
||||
<function model="account.invoice" name="action_invoice_open">
|
||||
<value eval="[ref('demo_invoice_january_wages')]"/>
|
||||
</function>
|
||||
|
||||
<record id="demo_invoice_equipment_purchase" model="account.invoice">
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="type">in_invoice</field>
|
||||
<field name="date_invoice" eval="time.strftime('%Y-%m')+'-15'"/>
|
||||
</record>
|
||||
|
||||
<record id="coffee_machine_line" model="account.invoice.line">
|
||||
<field name="invoice_id" ref="demo_invoice_equipment_purchase"/>
|
||||
<field name="name">Coffee Machine with huge 'employee\'s performances boosting perk'</field>
|
||||
<field name="price_unit">4999.99</field>
|
||||
<field name="account_id" model="account.account"
|
||||
search="['|', ('tag_ids', '=', ref('account.demo_coffee_machine_account')),
|
||||
('user_type_id', '=', ref('account.data_account_type_expenses')),
|
||||
('company_id', '=', obj().env['res.company']._company_default_get('account.account').id)]"/>
|
||||
<field name="invoice_line_tax_ids" model="account.tax"
|
||||
eval="[(6, 0, [obj().search([
|
||||
('type_tax_use', '=', 'purchase'),
|
||||
('amount', '>', 0),
|
||||
('company_id', '=', obj().env['res.company']._company_default_get('account.tax').id)], limit=1).id])]"/>
|
||||
</record>
|
||||
|
||||
<function model="account.invoice" name="_onchange_invoice_line_ids">
|
||||
<value eval="[ref('demo_invoice_equipment_purchase')]"/>
|
||||
</function>
|
||||
|
||||
<function model="account.invoice" name="action_invoice_open">
|
||||
<value eval="[ref('demo_invoice_equipment_purchase')]"/>
|
||||
</function>
|
||||
|
||||
<record id="demo_bank_statement_line_5" model="account.bank.statement.line">
|
||||
<field name="account_id" model="account.account" search="[
|
||||
('user_type_id', '=', ref('account.data_account_type_revenue')),
|
||||
('tag_ids', 'in', [ref('account.account_tag_financing')]),
|
||||
('company_id', '=', obj().env['res.company']._company_default_get('account.account').id)]"/>
|
||||
</record>
|
||||
|
||||
<function model="account.bank.statement.line" name="fast_counterpart_creation">
|
||||
<value eval="[ref('demo_bank_statement_line_5')]"/>
|
||||
</function>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -15,7 +15,6 @@
|
||||
'security/mrp_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'data/mrp_data.xml',
|
||||
'data/mrp_data.yml',
|
||||
'wizard/mrp_product_produce_views.xml',
|
||||
'wizard/change_production_qty_views.xml',
|
||||
'wizard/mrp_workcenter_block_view.xml',
|
||||
@@ -44,7 +43,7 @@
|
||||
],
|
||||
'demo': [
|
||||
'data/mrp_demo.xml',
|
||||
'data/mrp_lot_demo.yml'],
|
||||
],
|
||||
'qweb': ['static/src/xml/mrp.xml'],
|
||||
'test': [],
|
||||
'application': True,
|
||||
|
||||
@@ -82,5 +82,22 @@
|
||||
<field name="manual" eval="False"/>
|
||||
<field name="sequence">0</field>
|
||||
</record>
|
||||
|
||||
<function model="ir.model.data" name="create">
|
||||
<value model="stock.warehouse" eval="{
|
||||
'name': 'picking_type_manufacturing',
|
||||
'module':'mrp',
|
||||
'model': 'stock.picking.type',
|
||||
'res_id': obj().env.ref('stock.warehouse0').manu_type_id.id,
|
||||
}"/>
|
||||
</function>
|
||||
|
||||
<!-- avoid the xml id and the associated resource being dropped by the orm by manually making a hit on it -->
|
||||
<function model="ir.model.data" name="_update_dummy">
|
||||
<value>stock.picking.type</value>
|
||||
<value>mrp</value>
|
||||
<value>picking_type_manufacturing</value>
|
||||
</function>
|
||||
</data>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
-
|
||||
!python {model: ir.model.data, id: False}: |
|
||||
warehouse = self.env['stock.warehouse'].browse(ref('stock.warehouse0'))
|
||||
#create xml ids for demo data that are widely used in tests or in other codes, for more convenience
|
||||
xml_references = [
|
||||
{'name':'picking_type_manufacturing','module':'mrp', 'model':'stock.picking.type','res_id': warehouse.manu_type_id.id},
|
||||
]
|
||||
for xml_record in xml_references:
|
||||
xml_ids = self.search([('module', '=', xml_record['module']), ('model', '=', xml_record['model']), ('name', '=', xml_record['name'])]).ids
|
||||
if not xml_ids:
|
||||
self.create(xml_record)
|
||||
#avoid the xml id and the associated resource being dropped by the orm by manually making a hit on it
|
||||
self._update_dummy(xml_record['model'], xml_record['module'], xml_record['name'])
|
||||
@@ -588,7 +588,31 @@
|
||||
<field name="date_start" eval="(datetime.now() - timedelta(hours=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
</record>
|
||||
|
||||
<function model="mrp.production" name="action_assign">
|
||||
<value eval="[ref('mrp.mrp_production_laptop_cust')]"/>
|
||||
</function>
|
||||
|
||||
<function model="mrp.product.produce" name="create">
|
||||
<value model="mrp.product.produce" eval="dict(
|
||||
obj().with_context(active_id=ref('mrp.mrp_production_laptop_cust')).default_get(list(obj().fields_get())),
|
||||
**{
|
||||
'product_qty': obj().env.ref('mrp.mrp_production_laptop_cust').product_qty,
|
||||
'lot_id': ref('mrp.lot_product_27_0'),
|
||||
}
|
||||
)"/>
|
||||
</function>
|
||||
|
||||
<function model="mrp.product.produce" name="do_produce">
|
||||
<value model="mrp.product.produce" search="[('production_id', '=', obj().env.ref('mrp.mrp_production_laptop_cust').id)]"/>
|
||||
</function>
|
||||
|
||||
<function model="mrp.production" name="post_inventory">
|
||||
<value eval="[ref('mrp.mrp_production_laptop_cust')]"/>
|
||||
</function>
|
||||
|
||||
<function model="mrp.production" name="button_mark_done">
|
||||
<value eval="[ref('mrp.mrp_production_laptop_cust')]"/>
|
||||
</function>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
-
|
||||
!python {model: mrp.production, id: mrp_production_laptop_cust}: |
|
||||
if self.state != 'done':
|
||||
self.action_assign()
|
||||
wiz_obj = self.env['mrp.product.produce']
|
||||
ctx = dict(self.env.context, active_id=self.id)
|
||||
produce_wiz = wiz_obj.with_context(ctx).create({'product_qty': self.product_qty, 'lot_id': ref('lot_product_27_0')})
|
||||
produce_wiz.produce_line_ids.write({'qty_done': produce_wiz.product_qty})
|
||||
#Produce Product,Post Inventory and Set MO Done.
|
||||
produce_wiz.do_produce()
|
||||
self.post_inventory()
|
||||
self.button_mark_done()
|
||||
@@ -35,7 +35,7 @@ The following topics are covered by this module:
|
||||
'data/ir_sequence_data.xml',
|
||||
'data/mrp_repair_data.xml',
|
||||
],
|
||||
'demo': ['data/mrp_repair_demo.yml'],
|
||||
'demo': ['data/mrp_repair_demo.xml'],
|
||||
'test': [],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="mrp_repair_rmrp1" model="mrp.repair">
|
||||
<field name="address_id" ref="base.res_partner_address_1"/>
|
||||
<field name="guarantee_limit" eval="datetime.today().strftime('%Y-%m-%d')"/>
|
||||
<field name="invoice_method">none</field>
|
||||
<field name="product_id" ref="product.product_product_3"/>
|
||||
<field name="product_uom" ref="product.product_uom_unit"/>
|
||||
<field name="partner_invoice_id" ref="base.res_partner_address_1"/>
|
||||
<field name="location_dest_id" ref="base.res_partner_address_1"/>
|
||||
<field name="location_id" ref="stock.stock_location_stock"/>
|
||||
<field name="operations" model="mrp.repair.line" eval="[(5, 0, 0), (0, 0, {
|
||||
'location_dest_id': obj().env.ref('stock.location_production').id,
|
||||
'location_id': obj().env.ref('stock.stock_location_stock').id,
|
||||
'name': '[M-Las] Mouse, Laser',
|
||||
'product_id': obj().env.ref('product.product_product_11').id,
|
||||
'product_uom': obj().env.ref('product.product_uom_unit').id,
|
||||
'product_uom_qty': '1.0',
|
||||
'price_unit': 50.0,
|
||||
'state': 'draft',
|
||||
'type': 'add',
|
||||
})]"/>
|
||||
<field name="fees_lines" model="mrp.repair.fee" eval="[(5, 0, 0), (0, 0, {
|
||||
'name': 'Laptop formatting fees',
|
||||
'product_id': obj().env.ref('product.product_product_11c').id,
|
||||
'product_uom_qty': 1.0,
|
||||
'product_uom': obj().env.ref('product.product_uom_unit').id,
|
||||
'price_unit': 50.0,
|
||||
})]"/>
|
||||
<field name="partner_id" ref="base.res_partner_12"/>
|
||||
</record>
|
||||
|
||||
<record id="mrp_repair_rmrp0" model="mrp.repair">
|
||||
<field name="product_id" ref="product.product_product_5"/>
|
||||
<field name="product_uom" ref="product.product_uom_unit"/>
|
||||
<field name="address_id" ref="base.res_partner_address_1"/>
|
||||
<field name="guarantee_limit" eval="datetime.today().strftime('%Y-%m-%d')"/>
|
||||
<field name="invoice_method">after_repair</field>
|
||||
<field name="partner_invoice_id" ref="base.res_partner_address_1"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_14"/>
|
||||
<field name="location_id" ref="stock.stock_location_stock"/>
|
||||
<field name="operations" model="mrp.repair.line" eval="[(5, 0, 0), (0, 0, {
|
||||
'location_dest_id': obj().env.ref('stock.location_production').id,
|
||||
'location_id': obj().env.ref('stock.stock_location_stock').id,
|
||||
'name': '[M-Wir] Mouse, Wireless',
|
||||
'price_unit': 50.0,
|
||||
'product_id': obj().env.ref('product.product_product_12').id,
|
||||
'product_uom': obj().env.ref('product.product_uom_unit').id,
|
||||
'product_uom_qty': 1.0,
|
||||
'state': 'draft',
|
||||
'type': 'add',
|
||||
})]"/>
|
||||
<field name="fees_lines" model="mrp.repair.fee" eval="[(5, 0, 0), (0, 0, {
|
||||
'name': 'Monitor screen repair',
|
||||
'product_id': obj().env.ref('product.product_product_5c').id,
|
||||
'product_uom_qty': 1.0,
|
||||
'product_uom': obj().env.ref('product.product_uom_unit').id,
|
||||
'price_unit': 50.0,
|
||||
})]"/>
|
||||
<field name="partner_id" ref="base.res_partner_12"/>
|
||||
</record>
|
||||
|
||||
<record id="mrp_repair_rmrp2" model="mrp.repair">
|
||||
<field name="product_id" ref="product.product_product_6"/>
|
||||
<field name="product_uom" ref="product.product_uom_unit"/>
|
||||
<field name="address_id" ref="base.res_partner_address_1"/>
|
||||
<field name="guarantee_limit" eval="datetime.today().strftime('%Y-%m-%d')"/>
|
||||
<field name="invoice_method">b4repair</field>
|
||||
<field name="partner_invoice_id" ref="base.res_partner_address_1"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_stock"/>
|
||||
<field name="location_id" ref="stock.stock_location_14"/>
|
||||
<field name="operations" model="mrp.repair.line" eval="[(5, 0, 0), (0, 0, {
|
||||
'location_dest_id': obj().env.ref('stock.location_production').id,
|
||||
'location_id': obj().env.ref('stock.stock_location_stock').id,
|
||||
'name': '[RAM-SR5] RAM DDR SR5',
|
||||
'price_unit': 50.0,
|
||||
'product_id': obj().env.ref('product.product_product_13').id,
|
||||
'product_uom': obj().env.ref('product.product_uom_unit').id,
|
||||
'product_uom_qty': 1.0,
|
||||
'state': 'draft',
|
||||
'type': 'add',
|
||||
})]"/>
|
||||
<field name="fees_lines" model="mrp.repair.fee" eval="[(5, 0, 0), (0, 0, {
|
||||
'name': 'SIM card holder repair',
|
||||
'product_id': obj().env.ref('product.product_product_6c').id,
|
||||
'product_uom_qty': 1.0,
|
||||
'product_uom': obj().env.ref('product.product_uom_unit').id,
|
||||
'price_unit': 50.0,
|
||||
})]"/>
|
||||
<field name="partner_id" ref="base.res_partner_12"/>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -1,81 +0,0 @@
|
||||
-
|
||||
!record {model: mrp.repair, id: mrp_repair_rmrp1}:
|
||||
address_id: base.res_partner_address_1
|
||||
guarantee_limit: !eval datetime.today().strftime("%Y-%m-%d")
|
||||
invoice_method: 'none'
|
||||
product_id: product.product_product_3
|
||||
product_uom: product.product_uom_unit
|
||||
partner_invoice_id: base.res_partner_address_1
|
||||
location_dest_id: stock.stock_location_14
|
||||
location_id: stock.stock_location_stock
|
||||
operations:
|
||||
- location_dest_id: stock.location_production
|
||||
location_id: stock.stock_location_stock
|
||||
name: '[M-Las] Mouse, Laser'
|
||||
price_unit: 50.0
|
||||
product_id: product.product_product_11
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 1.0
|
||||
state: draft
|
||||
type: add
|
||||
fees_lines:
|
||||
- name: 'Laptop formatting fees'
|
||||
product_id: product.product_product_11c
|
||||
product_uom_qty: 1.0
|
||||
product_uom: product.product_uom_unit
|
||||
price_unit: 50.0
|
||||
partner_id: base.res_partner_12
|
||||
-
|
||||
!record {model: mrp.repair, id: mrp_repair_rmrp0}:
|
||||
product_id: product.product_product_5
|
||||
product_uom: product.product_uom_unit
|
||||
address_id: base.res_partner_address_1
|
||||
guarantee_limit: !eval datetime.today().strftime("%Y-%m-%d")
|
||||
invoice_method: 'after_repair'
|
||||
partner_invoice_id: base.res_partner_address_1
|
||||
location_dest_id: stock.stock_location_14
|
||||
location_id: stock.stock_location_stock
|
||||
operations:
|
||||
- location_dest_id: stock.location_production
|
||||
location_id: stock.stock_location_stock
|
||||
name: '[M-Wir] Mouse, Wireless'
|
||||
price_unit: 50.0
|
||||
product_id: product.product_product_12
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 1.0
|
||||
state: draft
|
||||
type: add
|
||||
fees_lines:
|
||||
- name: 'Monitor screen repair'
|
||||
product_id: product.product_product_5c
|
||||
product_uom_qty: 1.0
|
||||
product_uom: product.product_uom_unit
|
||||
price_unit: 50.0
|
||||
partner_id: base.res_partner_12
|
||||
-
|
||||
!record {model: mrp.repair, id: mrp_repair_rmrp2}:
|
||||
product_id: product.product_product_6
|
||||
product_uom: product.product_uom_unit
|
||||
address_id: base.res_partner_address_1
|
||||
guarantee_limit: !eval datetime.today().strftime("%Y-%m-%d")
|
||||
invoice_method: 'b4repair'
|
||||
partner_invoice_id: base.res_partner_address_1
|
||||
location_dest_id: stock.stock_location_stock
|
||||
location_id: stock.stock_location_14
|
||||
operations:
|
||||
- location_dest_id: stock.location_production
|
||||
location_id: stock.stock_location_stock
|
||||
name: '[RAM-SR5] RAM DDR SR5'
|
||||
price_unit: 50.0
|
||||
product_id: product.product_product_13
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 1.0
|
||||
state: draft
|
||||
type: add
|
||||
fees_lines:
|
||||
- name: 'SIM card holder repair'
|
||||
product_id: product.product_product_6c
|
||||
product_uom_qty: 1.0
|
||||
product_uom: product.product_uom_unit
|
||||
price_unit: 50.0
|
||||
partner_id: base.res_partner_12
|
||||
@@ -15,7 +15,6 @@
|
||||
'security/ir.model.access.csv',
|
||||
'views/account_invoice_views.xml',
|
||||
'data/purchase_data.xml',
|
||||
'data/purchase_data.yml',
|
||||
'report/purchase_reports.xml',
|
||||
'views/purchase_views.xml',
|
||||
'views/stock_views.xml',
|
||||
@@ -28,22 +27,8 @@
|
||||
'report/purchase_order_templates.xml',
|
||||
'report/purchase_quotation_templates.xml',
|
||||
],
|
||||
'test': [
|
||||
'../account/test/account_minimal_test.xml',
|
||||
'test/stock_valuation_account.xml',
|
||||
'test/ui/purchase_users.yml',
|
||||
'test/process/run_scheduler.yml',
|
||||
'test/fifo_price.yml',
|
||||
'test/fifo_returns.yml',
|
||||
'test/process/cancel_order.yml',
|
||||
'test/ui/duplicate_order.yml',
|
||||
'test/ui/delete_order.yml',
|
||||
'test/average_price.yml',
|
||||
],
|
||||
'demo': [
|
||||
'data/purchase_order_demo.yml',
|
||||
'data/purchase_demo.xml',
|
||||
'data/purchase_stock_demo.yml',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
|
||||
@@ -36,5 +36,10 @@
|
||||
<field name="padding">5</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- enable purchase on main warehouse -->
|
||||
<record id="stock.warehouse0" model="stock.warehouse">
|
||||
<field name="buy_to_resupply" eval="True"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
-
|
||||
!python {model: stock.warehouse, id: stock.warehouse0}: |
|
||||
#enable purchase on main warehouse
|
||||
self.write({'buy_to_resupply': True})
|
||||
|
||||
@@ -22,5 +22,170 @@
|
||||
<field name="route_ids" eval="[(4,ref('route_warehouse0_buy'))]"></field>
|
||||
</record>
|
||||
</data>
|
||||
|
||||
<record id="purchase_order_1" model="purchase.order">
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="state">draft</field>
|
||||
<field name="order_line" model="purchase.order.line" eval="[(5, 0, 0),
|
||||
(0, 0, {
|
||||
'product_id': ref('product.product_delivery_01'),
|
||||
'name': 'RAM SR3',
|
||||
'price_unit': 79.80,
|
||||
'product_qty': 15.0,
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'date_planned': time.strftime('%Y-%m-%d')}),
|
||||
(0, 0, {
|
||||
'product_id': ref('product.product_product_25'),
|
||||
'name': 'Laptop E5023',
|
||||
'price_unit': 2868.70,
|
||||
'product_qty': 5.0,
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'date_planned': time.strftime('%Y-%m-%d')}),
|
||||
(0, 0, {
|
||||
'product_id': ref('product.product_product_27'),
|
||||
'name': 'Laptop Customized',
|
||||
'price_unit': 3297.20,
|
||||
'product_qty': 4.0,
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'date_planned': time.strftime('%Y-%m-%d')})
|
||||
]"/>
|
||||
</record>
|
||||
|
||||
<record id="purchase_order_2" model="purchase.order">
|
||||
<field name="partner_id" ref="base.res_partner_3"/>
|
||||
<field name="state">draft</field>
|
||||
<field name="order_line" model="purchase.order.line" eval="[(5, 0, 0),
|
||||
(0, 0, {
|
||||
'product_id': ref('product.product_delivery_02'),
|
||||
'name': 'Multimedia Speakers',
|
||||
'price_unit': 132.50,
|
||||
'product_qty': 20.0,
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'date_planned': time.strftime('%Y-%m-%d')}),
|
||||
(0, 0, {
|
||||
'product_id': ref('product.product_delivery_01'),
|
||||
'name': 'Pen drive, SP-2',
|
||||
'price_unit': 89.0,
|
||||
'product_qty': 5.0,
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'date_planned': time.strftime('%Y-%m-%d')}),
|
||||
]"/>
|
||||
</record>
|
||||
|
||||
<record id="purchase_order_3" model="purchase.order">
|
||||
<field name="partner_id" ref="base.res_partner_12"/>
|
||||
<field name="state">draft</field>
|
||||
<field name="order_line" model="purchase.order.line" eval="[(5, 0, 0),
|
||||
(0, 0, {
|
||||
'product_id': ref('product.product_product_2'),
|
||||
'name': 'Support Services',
|
||||
'price_unit': 25.50,
|
||||
'product_qty': 10.0,
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'date_planned': time.strftime('%Y-%m-%d')}),
|
||||
]"/>
|
||||
</record>
|
||||
|
||||
<record id="purchase_order_4" model="purchase.order">
|
||||
<field name="partner_id" ref="base.res_partner_4"/>
|
||||
<field name="state">draft</field>
|
||||
<field name="order_line" model="purchase.order.line" eval="[(5, 0, 0),
|
||||
(0, 0, {
|
||||
'product_id': ref('product.product_delivery_02'),
|
||||
'name': 'RAM SR2 (kit)',
|
||||
'price_unit': 85.50,
|
||||
'product_qty': 6.0,
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'date_planned': time.strftime('%Y-%m-%d')}),
|
||||
(0, 0, {
|
||||
'product_id': ref('product.product_product_20'),
|
||||
'name': 'Motherboard I9P57',
|
||||
'price_unit': 1690.0,
|
||||
'product_qty': 5.0,
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'date_planned': time.strftime('%Y-%m-%d')}),
|
||||
(0, 0, {
|
||||
'product_id': ref('product.product_product_6'),
|
||||
'name': 'iPad Mini',
|
||||
'price_unit': 800.0,
|
||||
'product_qty': 7.0,
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'date_planned': time.strftime('%Y-%m-%d')})
|
||||
]"/>
|
||||
</record>
|
||||
|
||||
<record id="purchase_order_5" model="purchase.order">
|
||||
<field name="partner_id" ref="base.res_partner_2"/>
|
||||
<field name="state">draft</field>
|
||||
<field name="order_line" model="purchase.order.line" eval="[(5, 0, 0),
|
||||
(0, 0, {
|
||||
'product_id': ref('product.product_product_22'),
|
||||
'name': 'Processor Core i5 2.70 Ghz',
|
||||
'price_unit': 2010.0,
|
||||
'product_qty': 3.0,
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'date_planned': time.strftime('%Y-%m-%d')}),
|
||||
(0, 0, {
|
||||
'product_id': ref('product.product_product_24'),
|
||||
'name': 'Graphics Card',
|
||||
'price_unit': 876.0,
|
||||
'product_qty': 3.0,
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'date_planned': time.strftime('%Y-%m-%d')}),
|
||||
]"/>
|
||||
</record>
|
||||
|
||||
<record id="purchase_order_6" model="purchase.order">
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="state">draft</field>
|
||||
<field name="order_line" model="purchase.order.line" eval="[(5, 0, 0),
|
||||
(0, 0, {
|
||||
'product_id': ref('product.product_delivery_02'),
|
||||
'name': 'Ink Cartridge',
|
||||
'price_unit': 58.0,
|
||||
'product_qty': 9.0,
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'date_planned': time.strftime('%Y-%m-%d')}),
|
||||
(0, 0, {
|
||||
'product_id': ref('product.product_delivery_01'),
|
||||
'name': 'Toner Cartridge',
|
||||
'price_unit': 65.0,
|
||||
'product_qty': 3.0,
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'date_planned': time.strftime('%Y-%m-%d')}),
|
||||
(0, 0, {
|
||||
'product_id': ref('product.consu_delivery_01'),
|
||||
'name': 'GrapWorks Software',
|
||||
'price_unit': 154.5,
|
||||
'product_qty': 4.0,
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'date_planned': time.strftime('%Y-%m-%d')}),
|
||||
]"/>
|
||||
</record>
|
||||
|
||||
<record id="purchase_order_7" model="purchase.order">
|
||||
<field name="partner_id" ref="base.res_partner_4"/>
|
||||
<field name="state">draft</field>
|
||||
<field name="order_line" model="purchase.order.line" eval="[(5, 0, 0),
|
||||
(0, 0, {
|
||||
'product_id': ref('product.product_product_12'),
|
||||
'name': 'Mouse, Wireless',
|
||||
'price_unit': 13.5,
|
||||
'product_qty': 5.0,
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'date_planned': time.strftime('%Y-%m-%d')}),
|
||||
(0, 0, {
|
||||
'product_id': ref('product.product_delivery_02'),
|
||||
'name': 'Webcam',
|
||||
'price_unit': 38.0,
|
||||
'product_qty': 15.0,
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'date_planned': time.strftime('%Y-%m-%d')}),
|
||||
]"/>
|
||||
</record>
|
||||
|
||||
<record id="stock.stock_warehouse_shop0" model="stock.warehouse">
|
||||
<field name="buy_to_resupply" eval="True"/>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
-
|
||||
Give access rights of Purchase user to create purchase order
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_1}:
|
||||
partner_id: base.res_partner_1
|
||||
-
|
||||
!record {model: purchase.order.line, id: purchase_order_line_1}:
|
||||
order_id: purchase.purchase_order_1
|
||||
product_id: product.product_delivery_01
|
||||
name: RAM SR3
|
||||
price_unit: 79.80
|
||||
product_qty: 15.0
|
||||
product_uom: product.product_uom_unit
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
!record {model: purchase.order.line, id: purchase_order_line_2}:
|
||||
order_id: purchase_order_1
|
||||
product_id: product.product_product_25
|
||||
name: Laptop E5023
|
||||
price_unit: 2868.70
|
||||
product_qty: 5.0
|
||||
product_uom: product.product_uom_unit
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
!record {model: purchase.order.line, id: purchase_order_line_3}:
|
||||
order_id: purchase_order_1
|
||||
product_id: product.product_product_27
|
||||
name: Laptop Customized
|
||||
price_unit: 3297.20
|
||||
product_qty: 4.0
|
||||
product_uom: product.product_uom_unit
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
Creating second order
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_2}:
|
||||
partner_id: base.res_partner_3
|
||||
order_line:
|
||||
- product_id: product.product_delivery_02
|
||||
name: Multimedia Speakers
|
||||
price_unit: 132.50
|
||||
product_qty: 20.0
|
||||
product_uom: product.product_uom_unit
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
- product_id: product.product_delivery_01
|
||||
name: Pen drive, SP-2
|
||||
price_unit: 89
|
||||
product_qty: 5
|
||||
product_uom: product.product_uom_unit
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_3}:
|
||||
partner_id: base.res_partner_12
|
||||
order_line:
|
||||
- product_id: product.product_product_2
|
||||
name: Support Services
|
||||
price_unit: 25.50
|
||||
product_qty: 10.0
|
||||
product_uom: product.product_uom_unit
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_4}:
|
||||
partner_id: base.res_partner_4
|
||||
order_line:
|
||||
- product_id: product.product_delivery_02
|
||||
name: RAM SR2 (kit)
|
||||
price_unit: 85.50
|
||||
product_qty: 6.0
|
||||
product_uom: product.product_uom_unit
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
- product_id: product.product_product_20
|
||||
name: Motherboard I9P57
|
||||
price_unit: 1690
|
||||
product_qty: 5.0
|
||||
product_uom: product.product_uom_unit
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
- product_id: product.product_product_6
|
||||
name: iPad Mini
|
||||
price_unit: 800
|
||||
product_qty: 7.0
|
||||
product_uom: product.product_uom_unit
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_5}:
|
||||
partner_id: base.res_partner_2
|
||||
order_line:
|
||||
- product_id: product.product_product_22
|
||||
name: Processor Core i5 2.70 Ghz
|
||||
product_qty: 3
|
||||
price_unit: 2010
|
||||
product_uom: product.product_uom_unit
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
- product_id: product.product_product_24
|
||||
name: Graphics Card
|
||||
product_qty: 3
|
||||
price_unit: 876
|
||||
product_uom: product.product_uom_unit
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_6}:
|
||||
partner_id: base.res_partner_1
|
||||
order_line:
|
||||
- product_id: product.product_delivery_02
|
||||
name: Ink Cartridge
|
||||
price_unit: 58
|
||||
product_qty: 9
|
||||
product_uom: product.product_uom_unit
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
- product_id: product.product_delivery_01
|
||||
name: Toner Cartridge
|
||||
product_qty: 3
|
||||
price_unit: 65
|
||||
product_uom: product.product_uom_unit
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
- product_id: product.consu_delivery_01
|
||||
name: GrapWorks Software
|
||||
product_qty: 4
|
||||
price_unit: 154.5
|
||||
product_uom: product.product_uom_unit
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_7}:
|
||||
partner_id: base.res_partner_4
|
||||
order_line:
|
||||
- product_id: product.product_product_12
|
||||
name: Mouse, Wireless
|
||||
product_qty: 5
|
||||
price_unit: 13.5
|
||||
product_uom: product.product_uom_unit
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
- product_id: product.product_delivery_02
|
||||
name: Webcam
|
||||
product_qty: 15
|
||||
price_unit: 38.0
|
||||
product_uom: product.product_uom_unit
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
@@ -1,8 +0,0 @@
|
||||
-
|
||||
!python {model: stock.warehouse, id: False}: |
|
||||
pull_obj = self.env['procurement.rule']
|
||||
wh_to_assign = ['stock_warehouse_shop0']
|
||||
for wh_ref in wh_to_assign:
|
||||
warehouse = self.browse(ref('stock.' + wh_ref))
|
||||
#Force the rewriting of route and rule
|
||||
warehouse.write({'buy_to_resupply': True})
|
||||
@@ -1,134 +0,0 @@
|
||||
-
|
||||
Set a product as using average price.
|
||||
-
|
||||
!record {model: product.product, id: product_average_icecream}:
|
||||
default_code: AVG
|
||||
name: Average Ice Cream
|
||||
type: product
|
||||
categ_id: product.product_category_1
|
||||
uom_id: product.product_uom_kgm
|
||||
uom_po_id: product.product_uom_kgm
|
||||
valuation: real_time
|
||||
cost_method: average
|
||||
property_stock_account_input: o_expense
|
||||
property_stock_account_output: o_income
|
||||
supplier_taxes_id: []
|
||||
description: Average Ice Cream can be mass-produced and thus is widely available in developed parts of the world. Ice cream can be purchased in large cartons (vats and squrounds) from supermarkets and grocery stores, in smaller quantities from ice cream shops, convenience stores, and milk bars, and in individual servings from small carts or vans at public events.
|
||||
-
|
||||
I create a draft Purchase Order for first incoming shipment for 10 pieces at 60€
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_average1}:
|
||||
partner_id: base.res_partner_3
|
||||
order_line:
|
||||
- product_id: product_average_icecream
|
||||
product_qty: 10.0
|
||||
product_uom: product.product_uom_kgm
|
||||
price_unit: 60.0
|
||||
name: 'Average Ice Cream'
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
I confirm the first purchase order
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_average1}: |
|
||||
self.button_confirm()
|
||||
-
|
||||
I check the "Approved" status of purchase order 1
|
||||
-
|
||||
!assert {model: purchase.order, id: purchase_order_average1}:
|
||||
- state == 'purchase'
|
||||
-
|
||||
Process the reception of purchase order 1
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_average1}: |
|
||||
picking = self.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
-
|
||||
Check the average price of the product (average icecream).
|
||||
-
|
||||
!python {model: product.product, id: product_average_icecream}: |
|
||||
assert self.qty_available == 10.0, 'Wrong quantity in stock after first reception'
|
||||
assert self.standard_price == 60.0, 'Standard price should be the price of the first reception!'
|
||||
-
|
||||
I create a draft Purchase Order for second incoming shipment for 30 pieces at 80€
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_average2}:
|
||||
partner_id: base.res_partner_3
|
||||
order_line:
|
||||
- product_id: product_average_icecream
|
||||
product_qty: 30.0
|
||||
product_uom: product.product_uom_kgm
|
||||
price_unit: 80.0
|
||||
name: 'Average Ice Cream'
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
I confirm the second purchase order
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_average2}: |
|
||||
self.button_confirm()
|
||||
-
|
||||
Process the reception of purchase order 2
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_average2}: |
|
||||
picking = self.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
-
|
||||
Check the standard price
|
||||
-
|
||||
!python {model: product.product, id: product_average_icecream}: |
|
||||
assert self.standard_price == 75.0, 'After second reception, we should have an average price of 75.0 on the product'
|
||||
-
|
||||
Create picking to send some goods
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_average_shipment}:
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
Create move for picking
|
||||
-
|
||||
!record {model: stock.move, id: outgoing_shipment_average_icecream}:
|
||||
picking_id: outgoing_average_shipment
|
||||
product_id: product_average_icecream
|
||||
product_uom: product.product_uom_kgm
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
product_uom_qty: 20.0
|
||||
-
|
||||
I assign this outgoing shipment and process the delivery
|
||||
-
|
||||
!python {model: stock.picking, id: outgoing_average_shipment}: |
|
||||
self.action_assign()
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, self.id)]}).process()
|
||||
-
|
||||
Check the standard price (60 * 10 + 30 * 80) / 40 = 75.0 did not change
|
||||
-
|
||||
!python {model: product.product, id: product_average_icecream}: |
|
||||
assert self.standard_price == 75.0, 'Average price should not have changed with outgoing picking!'
|
||||
assert self.qty_available == 20.0, 'Pieces were not picked correctly as the quantity on hand is wrong'
|
||||
-
|
||||
Make a new purchase order with 500 g Average Ice Cream at a price of 0.2€/g
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_average3}:
|
||||
partner_id: base.res_partner_3
|
||||
order_line:
|
||||
- product_id: product_average_icecream
|
||||
product_qty: 0.5
|
||||
product_uom: product.product_uom_kgm
|
||||
price_unit: 200
|
||||
name: 'Average Ice Cream'
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
I confirm the first purchase order
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_average3}: |
|
||||
self.button_confirm()
|
||||
-
|
||||
Process the reception of purchase order 3 in grams
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_average3}: |
|
||||
picking = self.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
-
|
||||
Check price is (75.0*20 + 200*0.5) / 20.5 = 78.04878
|
||||
-
|
||||
!python {model: product.product, id: product_average_icecream}: |
|
||||
assert self.qty_available == 20.5, 'Reception of purchase order in grams leads to wrong quantity in stock'
|
||||
assert round(self.standard_price, 2) == 78.05, 'Standard price as average price of third reception with other UoM incorrect! Got %s instead of 78.05' % (round(self.standard_price, 2),)
|
||||
@@ -1,372 +0,0 @@
|
||||
-
|
||||
Set the company currency as EURO for the sake of repeatibility
|
||||
-
|
||||
!python {model: res.company}: |
|
||||
company = self.env.ref('base.main_company')
|
||||
company.currency_id = self.env.ref('base.EUR')
|
||||
-
|
||||
Set a product as using fifo price
|
||||
-
|
||||
!record {model: product.product, id: product_fifo_icecream}:
|
||||
default_code: FIFO
|
||||
name: FIFO Ice Cream
|
||||
type: product
|
||||
categ_id: product.product_category_1
|
||||
list_price: 100.0
|
||||
standard_price: 70.0
|
||||
uom_id: product.product_uom_kgm
|
||||
uom_po_id: product.product_uom_kgm
|
||||
cost_method: fifo
|
||||
valuation: real_time
|
||||
property_stock_account_input: o_expense
|
||||
property_stock_account_output: o_income
|
||||
supplier_taxes_id: []
|
||||
description: FIFO Ice Cream can be mass-produced and thus is widely available in developed parts of the world. Ice cream can be purchased in large cartons (vats and squrounds) from supermarkets and grocery stores, in smaller quantities from ice cream shops, convenience stores, and milk bars, and in individual servings from small carts or vans at public events.
|
||||
-
|
||||
I create a draft Purchase Order for first in move for 10 kg at 50 euro
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_fifo1}:
|
||||
partner_id: base.res_partner_3
|
||||
order_line:
|
||||
- product_id: product_fifo_icecream
|
||||
product_qty: 10.0
|
||||
product_uom: product.product_uom_kgm
|
||||
price_unit: 50.0
|
||||
name: 'FIFO Ice Cream'
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
I confirm the first purchase order
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_fifo1}: |
|
||||
self.button_confirm()
|
||||
-
|
||||
I check the "Purchase" status of purchase order 1
|
||||
-
|
||||
!assert {model: purchase.order, id: purchase_order_fifo1}:
|
||||
- state == 'purchase'
|
||||
-
|
||||
Process the reception of purchase order 1 and set date
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_fifo1}: |
|
||||
picking = self.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
-
|
||||
Check the standard price of the product (fifo icecream), that should have not changed because the standard price is supposed to be updated only when goods are going out of the stock
|
||||
-
|
||||
!python {model: product.product, id: product_fifo_icecream}: |
|
||||
assert self.standard_price == 70.0, 'Standard price should not have changed!'
|
||||
assert self.stock_value == 500.0, 'Wrong stock value'
|
||||
-
|
||||
I create a draft Purchase Order for second shipment for 30 kg at 80 euro
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_fifo2}:
|
||||
partner_id: base.res_partner_3
|
||||
order_line:
|
||||
- product_id: product_fifo_icecream
|
||||
product_qty: 30.0
|
||||
product_uom: product.product_uom_kgm
|
||||
price_unit: 80.0
|
||||
name: 'FIFO Ice Cream'
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
I confirm the second purchase order
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_fifo2}: |
|
||||
self.button_confirm()
|
||||
-
|
||||
Process the reception of purchase order 2
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_fifo2}: |
|
||||
picking = self.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
-
|
||||
Check the standard price of the product, that should have not changed because the standard price is supposed to be updated only when goods are going out of the stock
|
||||
-
|
||||
!python {model: product.product, id: product_fifo_icecream}: |
|
||||
assert self.standard_price == 70.0, 'Standard price as fifo price of second reception incorrect!'
|
||||
assert self.stock_value == 2900.0, 'Stock valuation should be 2900'
|
||||
-
|
||||
Let us send some goods
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_fifo_shipment}:
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
Picking needs movement from stock
|
||||
-
|
||||
!record {model: stock.move, id: outgoing_shipment_fifo_icecream}:
|
||||
picking_id: outgoing_fifo_shipment
|
||||
product_id: product_fifo_icecream
|
||||
product_uom: product.product_uom_kgm
|
||||
product_uom_qty: 20.0
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
I assign this outgoing shipment
|
||||
-
|
||||
!python {model: stock.picking, id: outgoing_fifo_shipment}: |
|
||||
self.action_assign()
|
||||
-
|
||||
Process the delivery of the outgoing shipment
|
||||
-
|
||||
!python {model: stock.picking, id: outgoing_fifo_shipment}: |
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, self.id)]}).process()
|
||||
-
|
||||
Check stock valuation is 1600
|
||||
-
|
||||
!python {model: product.product, id: product_fifo_icecream}: |
|
||||
assert self.stock_value == 1600.0, 'Stock valuation should be 1600'
|
||||
-
|
||||
Do a delivery of an extra 500 g (delivery order)
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_fifo_shipment_uom}:
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
Picking needs movement from stock
|
||||
-
|
||||
!record {model: stock.move, id: outgoing_shipment_fifo_icecream_gram}:
|
||||
picking_id: outgoing_fifo_shipment_uom
|
||||
product_id: product_fifo_icecream
|
||||
product_uom: product.product_uom_kgm
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
product_uom_qty: 0.5
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
I assign this outgoing shipment
|
||||
-
|
||||
!python {model: stock.picking, id: outgoing_fifo_shipment_uom}: |
|
||||
self.action_assign()
|
||||
-
|
||||
Process the delivery of the outgoing shipment
|
||||
-
|
||||
!python {model: stock.picking, id: outgoing_fifo_shipment_uom}: |
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, self.id)]}).process()
|
||||
-
|
||||
Check stock valuation and qty in stock
|
||||
-
|
||||
!python {model: product.product, id: product_fifo_icecream}: |
|
||||
assert self.stock_value == 1560.0, 'Stock valuation should be 1560'
|
||||
assert self.qty_available == 19.5, 'Should still have 19.5 in stock'
|
||||
-
|
||||
We will temporarily change the currency rate on the sixth of June to have the same results all year
|
||||
-
|
||||
!record {model: res.currency, id: new_USD}:
|
||||
name: new_usd
|
||||
symbol: "$"
|
||||
rate_ids:
|
||||
- rate: 1.2834
|
||||
name: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
I create a draft Purchase Order for 30000 g at 0.150 USD/g and 10 kg at 150 USD/kg
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_fifo_usd}:
|
||||
partner_id: base.res_partner_3
|
||||
currency_id: new_USD
|
||||
order_line:
|
||||
- product_id: product_fifo_icecream
|
||||
product_qty: 30
|
||||
product_uom: product.product_uom_kgm
|
||||
price_unit: 0.150
|
||||
name: 'FIFO Ice Cream'
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
- product_id: product_fifo_icecream
|
||||
product_qty: 10.0
|
||||
product_uom: product.product_uom_kgm
|
||||
price_unit: 150.0
|
||||
name: 'FIFO Ice Cream'
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
I confirm the purchase order in USD
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_fifo_usd}: |
|
||||
self.button_confirm()
|
||||
-
|
||||
Process the reception of purchase order with usd
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_fifo_usd}: |
|
||||
picking = self.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
-
|
||||
We create delivery order of 49.5 kg
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_fifo_shipment_cur}:
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
Picking needs movement from stock
|
||||
-
|
||||
!record {model: stock.move, id: outgoing_shipment_fifo_icecream_cur}:
|
||||
picking_id: outgoing_fifo_shipment_cur
|
||||
product_id: product_fifo_icecream
|
||||
product_uom: product.product_uom_kgm
|
||||
product_uom_qty: 49.5
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
I assign this outgoing shipment
|
||||
-
|
||||
!python {model: stock.picking, id: outgoing_fifo_shipment_cur}: |
|
||||
self.action_assign()
|
||||
-
|
||||
Process the delivery of the outgoing shipment
|
||||
-
|
||||
!python {model: stock.picking, id: outgoing_fifo_shipment_cur}: |
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, self.id)]}).process()
|
||||
-
|
||||
Do a delivery of an extra 10 kg
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_fifo_shipment_ret}:
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
Picking needs movement from stock
|
||||
-
|
||||
!record {model: stock.move, id: outgoing_shipment_fifo_icecream_ret}:
|
||||
picking_id: outgoing_fifo_shipment_ret
|
||||
product_id: product_fifo_icecream
|
||||
product_uom: product.product_uom_kgm
|
||||
product_uom_qty: 10.0
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
I assign this outgoing shipment
|
||||
-
|
||||
!python {model: stock.picking, id: outgoing_fifo_shipment_ret}: |
|
||||
self.action_assign()
|
||||
-
|
||||
Process the delivery of the outgoing shipment
|
||||
-
|
||||
!python {model: stock.picking, id: outgoing_fifo_shipment_ret}: |
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, self.id)]}).process()
|
||||
-
|
||||
Check rounded price is 150.0 / 1.2834
|
||||
-
|
||||
!python {model: product.product, id: product_fifo_icecream}: |
|
||||
assert self.qty_available == 0.0, 'Wrong quantity in stock after first reception'
|
||||
-
|
||||
Let us create some outs to get negative stock for a new product using the same config
|
||||
-
|
||||
!record {model: product.product, id: product_fifo_negative}:
|
||||
default_code: NEG
|
||||
name: FIFO Negative
|
||||
type: product
|
||||
categ_id: product.product_category_1
|
||||
list_price: 100.0
|
||||
standard_price: 70.0
|
||||
uom_id: product.product_uom_kgm
|
||||
uom_po_id: product.product_uom_kgm
|
||||
cost_method: fifo
|
||||
valuation: real_time
|
||||
property_stock_account_input: o_expense
|
||||
property_stock_account_output: o_income
|
||||
supplier_taxes_id: []
|
||||
description:
|
||||
-
|
||||
Create outpicking. We create delivery order of 100 kg.
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_fifo_shipment_neg}:
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
Picking needs movement from stock
|
||||
-
|
||||
!record {model: stock.move, id: outgoing_shipment_fifo_icecream_neg}:
|
||||
picking_id: outgoing_fifo_shipment_neg
|
||||
product_id: product_fifo_negative
|
||||
product_uom: product.product_uom_kgm
|
||||
product_uom_qty: 100.0
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
Process the delivery of the first outgoing shipment
|
||||
-
|
||||
!python {model: stock.picking, id: outgoing_fifo_shipment_neg}: |
|
||||
self.action_confirm()
|
||||
move = self.move_lines[0]
|
||||
move.quantity_done = 100
|
||||
self.action_done()
|
||||
assert move.product_id.qty_available == -100, 'Stock qty should be -100'
|
||||
-
|
||||
The behavior of fifo/lifo is not guaranteed if the quants are created at the same second, so I just wait one second
|
||||
-
|
||||
!python {model: stock.picking, id: False}: |
|
||||
import time
|
||||
time.sleep(1)
|
||||
-
|
||||
Let us create another out of 400 kg
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_fifo_shipment_neg2}:
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
Picking needs movement from stock
|
||||
-
|
||||
!record {model: stock.move, id: outgoing_shipment_fifo_icecream_neg2}:
|
||||
picking_id: outgoing_fifo_shipment_neg2
|
||||
product_id: product_fifo_negative
|
||||
product_uom: product.product_uom_kgm
|
||||
product_uom_qty: 400.0
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
Process the delivery of the outgoing shipments
|
||||
-
|
||||
!python {model: stock.picking, id: outgoing_fifo_shipment_neg2}: |
|
||||
self.action_confirm()
|
||||
move = self.move_lines[0]
|
||||
move.quantity_done = 400
|
||||
self.action_done()
|
||||
assert move.product_id.qty_available == -500, 'Stock qty should be -500'
|
||||
-
|
||||
Receive purchase order with 50 kg FIFO Ice Cream at 50 euro/kg
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_fifo_neg}:
|
||||
partner_id: base.res_partner_3
|
||||
order_line:
|
||||
- product_id: product_fifo_negative
|
||||
product_qty: 50.0
|
||||
product_uom: product.product_uom_kgm
|
||||
price_unit: 50.0
|
||||
name: 'FIFO Ice Cream'
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
I confirm the first purchase order
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_fifo_neg}: |
|
||||
self.button_confirm()
|
||||
-
|
||||
Process the reception of purchase order 1
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_fifo_neg}: |
|
||||
picking = self.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
-
|
||||
Receive purchase order with 600 kg FIFO Ice Cream at 80 euro/kg
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_fifo_neg2}:
|
||||
partner_id: base.res_partner_3
|
||||
order_line:
|
||||
- product_id: product_fifo_negative
|
||||
product_qty: 600.0
|
||||
product_uom: product.product_uom_kgm
|
||||
price_unit: 80
|
||||
name: 'FIFO Ice Cream'
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
I confirm the second negative purchase order
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_fifo_neg2}: |
|
||||
self.button_confirm()
|
||||
-
|
||||
Process the reception of purchase order 2
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_fifo_neg2}: |
|
||||
picking = self.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
original_out_move = self.env['stock.picking'].browse(ref('outgoing_fifo_shipment_neg')).move_lines[0]
|
||||
outgoing_shipment_fifo_icecream_neg2 = self.env['stock.picking'].browse(ref('outgoing_fifo_shipment_neg2')).move_lines[0]
|
||||
original_out_move._fifo_vacuum()
|
||||
outgoing_shipment_fifo_icecream_neg2._fifo_vacuum()
|
||||
assert original_out_move.product_id.stock_value == 12000.0, 'Stock Value should be 12000'
|
||||
assert original_out_move.product_id.qty_available == 150.0, 'Qty available should be 150'
|
||||
@@ -1,86 +0,0 @@
|
||||
-
|
||||
Set a product as using fifo price
|
||||
-
|
||||
!record {model: product.product, id: product_fiforet_icecream}:
|
||||
default_code: FIFORET
|
||||
name: FIFO Ice Cream
|
||||
type: product
|
||||
standard_price: 0.0
|
||||
categ_id: product.product_category_1
|
||||
uom_id: product.product_uom_kgm
|
||||
uom_po_id: product.product_uom_kgm
|
||||
cost_method: fifo
|
||||
valuation: real_time
|
||||
property_stock_account_input: o_expense
|
||||
property_stock_account_output: o_income
|
||||
supplier_taxes_id: []
|
||||
description: FIFO Ice Cream can be mass-produced and thus is widely available in developed parts of the world. Ice cream can be purchased in large cartons (vats and squrounds) from supermarkets and grocery stores, in smaller quantities from ice cream shops, convenience stores, and milk bars, and in individual servings from small carts or vans at public events.
|
||||
-
|
||||
I create a draft Purchase Order for first in move for 10 kg at 50 euro
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_fiforet1}:
|
||||
partner_id: base.res_partner_3
|
||||
order_line:
|
||||
- product_id: product_fiforet_icecream
|
||||
product_qty: 10.0
|
||||
product_uom: product.product_uom_kgm
|
||||
price_unit: 50.0
|
||||
name: 'FIFO Ice Cream'
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
I create a draft Purchase Order for second shipment for 30 kg at 80 euro
|
||||
-
|
||||
!record {model: purchase.order, id: purchase_order_fiforet2}:
|
||||
partner_id: base.res_partner_3
|
||||
order_line:
|
||||
- product_id: product_fiforet_icecream
|
||||
product_qty: 30.0
|
||||
product_uom: product.product_uom_kgm
|
||||
price_unit: 80.0
|
||||
name: 'FIFO Ice Cream'
|
||||
date_planned: !eval time.strftime('%Y-%m-%d')
|
||||
-
|
||||
I confirm the first purchase order
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_fiforet1}: |
|
||||
self.button_confirm()
|
||||
-
|
||||
Process the reception of purchase order 1
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_fiforet1}: |
|
||||
picking = self.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
-
|
||||
Check the standard price of the product (fifo icecream)
|
||||
-
|
||||
!python {model: product.product, id: product_fiforet_icecream}: |
|
||||
assert self.standard_price == 0.0, 'Standard price should not have changed!'
|
||||
-
|
||||
I confirm the second purchase order
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_fiforet2}: |
|
||||
self.button_confirm()
|
||||
-
|
||||
Process the reception of purchase order 2
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_fiforet2}: |
|
||||
picking = self.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
-
|
||||
Return the goods of purchase order 2
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_fiforet2}: |
|
||||
picking = self.picking_ids[0]
|
||||
return_id = self.env['stock.return.picking'].with_context(active_model='stock.picking', active_id=picking.id).create({})
|
||||
return_picking_id, dummy = return_id.with_context(active_id=picking.id)._create_returns()
|
||||
# Important to pass through confirmation and assignation
|
||||
return_picking = self.env['stock.picking'].browse(return_picking_id)
|
||||
return_picking.action_confirm()
|
||||
return_picking.move_lines[0].quantity_done = return_picking.move_lines[0].product_uom_qty
|
||||
return_picking.action_done()
|
||||
-
|
||||
After the return only 10 of the second purchase order should still be in stock as it applies fifo on the return too
|
||||
-
|
||||
!python {model: product.product, id: product_fiforet_icecream}: |
|
||||
assert self.qty_available == 10.0, 'Qty available should be 10.0'
|
||||
assert self.stock_value == 800, 'Stock value should be 800'
|
||||
@@ -1,32 +0,0 @@
|
||||
-
|
||||
Purchase user can also cancel order therfore test with that user which have Purchase user rights.
|
||||
-
|
||||
!context
|
||||
uid: 'res_users_purchase_user'
|
||||
-
|
||||
In order to test the cancel flow, I start it from canceling confirmed purchase order.
|
||||
-
|
||||
I confirm the purchase order.
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_5}: |
|
||||
self.button_confirm()
|
||||
-
|
||||
I check the "Approved" status after confirmed RFQ.
|
||||
-
|
||||
!assert {model: purchase.order, id: purchase_order_5}:
|
||||
- state == 'purchase'
|
||||
-
|
||||
First I cancel receptions related to this order if order shipped.
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_5}: |
|
||||
self.env['stock.picking'].browse([picking.id for picking in self.picking_ids]).action_cancel()
|
||||
-
|
||||
Now I am able to cancel purchase order.
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_5}: |
|
||||
self.button_cancel()
|
||||
-
|
||||
I check that order is cancelled.
|
||||
-
|
||||
!assert {model: purchase.order, id: purchase_order_5}:
|
||||
- state == 'cancel'
|
||||
@@ -1,15 +0,0 @@
|
||||
-
|
||||
In order to test the scheduler to generate RFQ, I create a new product
|
||||
-
|
||||
!record {model: product.product, id: scheduler_product, view: False}:
|
||||
name: scheduler prod
|
||||
type: product
|
||||
seller_ids:
|
||||
- delay: 1
|
||||
name: base.res_partner_2
|
||||
min_qty: 5.0
|
||||
-
|
||||
Add Buy route
|
||||
-
|
||||
!python {model: product.product, id: scheduler_product}: |
|
||||
self.write({"route_ids": [(4, ref("purchase.route_warehouse0_buy"))]})
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record forcecreate="True" id="property_stock_valuation_account_id" model="ir.property">
|
||||
<field name="name">property_stock_valuation_account_id</field>
|
||||
<field name="fields_id" search="[('model','=','product.category'),('name','=','property_stock_valuation_account_id')]"/>
|
||||
<field eval="'account.account,'+str(ref('stk'))" model="account.account" name="value"/>
|
||||
<field name="company_id" ref="base.main_company"/>
|
||||
</record>
|
||||
<record forcecreate="True" id="property_stock_journal" model="ir.property">
|
||||
<field name="name">property_stock_journal</field>
|
||||
<field name="fields_id" search="[('model','=','product.category'),('name','=','property_stock_journal'),('relation', '=', 'account.journal')]"/>
|
||||
<field eval="'account.journal,'+str(ref('miscellaneous_journal'))" model="account.journal" name="value"/>
|
||||
<field name="company_id" ref="base.main_company"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,31 +0,0 @@
|
||||
-
|
||||
Give access rights of Purchase user to delete purchase order
|
||||
-
|
||||
!context
|
||||
uid: 'res_users_purchase_user'
|
||||
-
|
||||
In order to test to delete process on purchase order.
|
||||
-
|
||||
I try to delete confirmed order and check Error Message.
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_1}: |
|
||||
try:
|
||||
self.unlink()
|
||||
except Exception:
|
||||
pass
|
||||
-
|
||||
I delete a cancelled order.
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_7}: |
|
||||
from odoo.exceptions import UserError
|
||||
try:
|
||||
self.unlink()
|
||||
except UserError:
|
||||
pass
|
||||
-
|
||||
I delete a draft order.
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_5}: |
|
||||
self.unlink()
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
-
|
||||
In order to test the duplicate order and check duplicate details.
|
||||
-
|
||||
I duplicate order.
|
||||
-
|
||||
!python {model: purchase.order, id: purchase_order_1}: |
|
||||
self.copy()
|
||||
@@ -1,28 +0,0 @@
|
||||
-
|
||||
Create a user as 'Purchase manager'
|
||||
-
|
||||
!record {model: res.users, id: res_users_purchase_manager, view: False}:
|
||||
company_id: base.main_company
|
||||
name: Purchase Manager
|
||||
login: pm
|
||||
email: purchasemanager@yourcompany.com
|
||||
-
|
||||
I added groups for Purchase manager.
|
||||
-
|
||||
!record {model: res.users, id: res_users_purchase_manager}:
|
||||
groups_id:
|
||||
- purchase.group_purchase_manager
|
||||
-
|
||||
Create a user as 'Purchase user'
|
||||
-
|
||||
!record {model: res.users, id: res_users_purchase_user, view: False}:
|
||||
company_id: base.main_company
|
||||
name: Purchase User
|
||||
login: pu
|
||||
email: purchaseuser@yourcompany.com
|
||||
-
|
||||
I added groups for Purchase user.
|
||||
-
|
||||
!record {model: res.users, id: res_users_purchase_user}:
|
||||
groups_id:
|
||||
- purchase.group_purchase_user
|
||||
@@ -1,8 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_onchange_product_id
|
||||
from . import test_purchase_order
|
||||
from . import test_average_price
|
||||
from . import test_create_picking
|
||||
from . import test_fifo_price
|
||||
from . import test_fifo_returns
|
||||
from . import test_onchange_product
|
||||
from . import test_purchase_delete_order
|
||||
from . import test_purchase_lead_time
|
||||
from . import test_purchase_order
|
||||
from . import test_purchase_order_process
|
||||
from . import test_stockvaluation
|
||||
|
||||
@@ -3,6 +3,8 @@ from datetime import timedelta
|
||||
|
||||
from odoo import fields
|
||||
from odoo.addons.stock.tests.common2 import TestStockCommon
|
||||
from odoo import tools
|
||||
from odoo.modules.module import get_module_resource
|
||||
|
||||
|
||||
class TestPurchase(TestStockCommon):
|
||||
@@ -16,6 +18,11 @@ class TestPurchase(TestStockCommon):
|
||||
}
|
||||
return ProcurementGroup.run(product, product_qty, self.uom_unit, self.warehouse_1.lot_stock_id, product.name, '/', order_values)
|
||||
|
||||
def _load(self, module, *args):
|
||||
tools.convert_file(self.cr, 'purchase',
|
||||
get_module_resource(module, *args),
|
||||
{}, 'init', False, 'test', self.registry._assertion_report)
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestPurchase, cls).setUpClass()
|
||||
@@ -34,3 +41,11 @@ class TestPurchase(TestStockCommon):
|
||||
'type': 'product',
|
||||
'route_ids': [(6, 0, [cls.route_buy, cls.route_mto])],
|
||||
'seller_ids': [(0, 0, {'name': cls.partner_1.id, 'delay': 2})]})
|
||||
|
||||
cls.res_users_purchase_user = cls.env['res.users'].create({
|
||||
'company_id': cls.env.ref('base.main_company').id,
|
||||
'name': "Purchase User",
|
||||
'login': "pu",
|
||||
'email': "purchaseuser@yourcompany.com",
|
||||
'groups_id': [(6, 0, [cls.env.ref('purchase.group_purchase_user').id])],
|
||||
})
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import time
|
||||
|
||||
from .common import TestPurchase
|
||||
|
||||
|
||||
class TestAveragePrice(TestPurchase):
|
||||
|
||||
def test_00_average_price(self):
|
||||
""" Testcase for average price computation"""
|
||||
|
||||
self._load('account', 'test', 'account_minimal_test.xml')
|
||||
self._load('stock_account', 'test', 'stock_valuation_account.xml')
|
||||
|
||||
# Set a product as using average price.
|
||||
product_icecream = self.env['product.product'].create({
|
||||
'default_code': 'AVG',
|
||||
'name': 'Average Ice Cream',
|
||||
'type': 'product',
|
||||
'categ_id': self.env.ref('product.product_category_1').id,
|
||||
'list_price': 100.0,
|
||||
'standard_price': 60.0,
|
||||
'uom_id': self.env.ref('product.product_uom_kgm').id,
|
||||
'uom_po_id': self.env.ref('product.product_uom_kgm').id,
|
||||
'cost_method': 'average',
|
||||
'valuation': 'real_time',
|
||||
'property_stock_account_input': self.ref('purchase.o_expense'),
|
||||
'property_stock_account_output': self.ref('purchase.o_income'),
|
||||
'supplier_taxes_id': [],
|
||||
'description': 'FIFO Ice Cream',
|
||||
})
|
||||
|
||||
# I create a draft Purchase Order for first incoming shipment for 10 pieces at 60€
|
||||
purchase_order_1 = self.env['purchase.order'].create({
|
||||
'partner_id': self.env.ref('base.res_partner_3').id,
|
||||
'order_line': [(0, 0, {
|
||||
'name': 'Average Ice Cream',
|
||||
'product_id': product_icecream.id,
|
||||
'product_qty': 10.0,
|
||||
'product_uom': self.env.ref('product.product_uom_kgm').id,
|
||||
'price_unit': 60.0,
|
||||
'date_planned': time.strftime('%Y-%m-%d'),
|
||||
})]
|
||||
})
|
||||
|
||||
# Confirm the first purchase order
|
||||
purchase_order_1.button_confirm()
|
||||
|
||||
# Check the "Approved" status of purchase order 1
|
||||
self.assertEqual(purchase_order_1.state, 'purchase', "Wrong state of purchase order!")
|
||||
|
||||
# Process the reception of purchase order 1
|
||||
picking = purchase_order_1.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
|
||||
# Check the average_price of the product (average icecream).
|
||||
self.assertEqual(product_icecream.qty_available, 10.0, 'Wrong quantity in stock after first reception')
|
||||
self.assertEqual(product_icecream.standard_price, 60.0, 'Standard price should be the price of the first reception!')
|
||||
|
||||
# I create a draft Purchase Order for second incoming shipment for 30 pieces at 80€
|
||||
purchase_order_2 = self.env['purchase.order'].create({
|
||||
'partner_id': self.env.ref('base.res_partner_3').id,
|
||||
'order_line': [(0, 0, {
|
||||
'name': product_icecream.name,
|
||||
'product_id': product_icecream.id,
|
||||
'product_qty': 30.0,
|
||||
'product_uom': self.env.ref('product.product_uom_kgm').id,
|
||||
'price_unit': 80.0,
|
||||
'date_planned': time.strftime('%Y-%m-%d'),
|
||||
})]
|
||||
})
|
||||
|
||||
# Confirm the second purchase order
|
||||
purchase_order_2.button_confirm()
|
||||
# Process the reception of purchase order 2
|
||||
picking = purchase_order_2.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
|
||||
# Check the standard price
|
||||
self.assertEqual(product_icecream.standard_price, 75.0, 'After second reception, we should have an average price of 75.0 on the product')
|
||||
|
||||
# Create picking to send some goods
|
||||
outgoing_shipment = self.env['stock.picking'].create({
|
||||
'picking_type_id': self.env.ref('stock.picking_type_out').id,
|
||||
'location_id': self.env.ref('stock.stock_location_stock').id,
|
||||
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
|
||||
'move_lines': [(0, 0, {
|
||||
'name': 'outgoing_shipment_avg_move',
|
||||
'product_id': product_icecream.id,
|
||||
'product_uom_qty': 20.0,
|
||||
'product_uom': self.env.ref('product.product_uom_kgm').id,
|
||||
'location_id': self.env.ref('stock.stock_location_stock').id,
|
||||
'location_dest_id': self.env.ref('stock.stock_location_customers').id})]
|
||||
})
|
||||
|
||||
# Assign this outgoing shipment and process the delivery
|
||||
outgoing_shipment.action_assign()
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, outgoing_shipment.id)]}).process()
|
||||
|
||||
# Check the average price (60 * 10 + 30 * 80) / 40 = 75.0€ did not change
|
||||
self.assertEqual(product_icecream.standard_price, 75.0, 'Average price should not have changed with outgoing picking!')
|
||||
self.assertEqual(product_icecream.qty_available, 20.0, 'Pieces were not picked correctly as the quantity on hand is wrong')
|
||||
|
||||
# Make a new purchase order with 500 g Average Ice Cream at a price of 0.2€/g
|
||||
purchase_order_3 = self.env['purchase.order'].create({
|
||||
'partner_id': self.env.ref('base.res_partner_3').id,
|
||||
'order_line': [(0, 0, {
|
||||
'name': product_icecream.name,
|
||||
'product_id': product_icecream.id,
|
||||
'product_qty': 500.0,
|
||||
'product_uom': self.ref('product.product_uom_gram'),
|
||||
'price_unit': 0.2,
|
||||
'date_planned': time.strftime('%Y-%m-%d'),
|
||||
})]
|
||||
})
|
||||
|
||||
# Confirm the first purchase order
|
||||
purchase_order_3.button_confirm()
|
||||
# Process the reception of purchase order 3 in grams
|
||||
picking = purchase_order_3.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
|
||||
# Check price is (75.0 * 20 + 200*0.5) / 20.5 = 78.04878€
|
||||
self.assertEqual(product_icecream.qty_available, 20.5, 'Reception of purchase order in grams leads to wrong quantity in stock')
|
||||
self.assertEqual(round(product_icecream.standard_price, 2), 78.05,
|
||||
'Standard price as average price of third reception with other UoM incorrect! Got %s instead of 78.05' % (round(product_icecream.standard_price, 2)))
|
||||
@@ -0,0 +1,323 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import time
|
||||
|
||||
from .common import TestPurchase
|
||||
|
||||
|
||||
class TestFifoPrice(TestPurchase):
|
||||
|
||||
def test_00_test_fifo(self):
|
||||
""" Test product cost price with fifo removal strategy."""
|
||||
|
||||
self._load('account', 'test', 'account_minimal_test.xml')
|
||||
self._load('stock_account', 'test', 'stock_valuation_account.xml')
|
||||
|
||||
# Set the company currency as EURO for the sake of repeatibility
|
||||
self.env.ref('base.main_company').currency_id = self.env.ref('base.EUR')
|
||||
|
||||
# Set a product as using fifo price
|
||||
product_icecream = self.env['product.product'].create({
|
||||
'default_code': 'FIFO',
|
||||
'name': 'FIFO Ice Cream',
|
||||
'type': 'product',
|
||||
'categ_id': self.env.ref('product.product_category_1').id,
|
||||
'list_price': 100.0,
|
||||
'standard_price': 70.0,
|
||||
'uom_id': self.env.ref('product.product_uom_kgm').id,
|
||||
'uom_po_id': self.env.ref('product.product_uom_kgm').id,
|
||||
'cost_method': 'fifo',
|
||||
'valuation': 'real_time',
|
||||
'property_stock_account_input': self.ref('purchase.o_expense'),
|
||||
'property_stock_account_output': self.ref('purchase.o_income'),
|
||||
'supplier_taxes_id': '[]',
|
||||
'description': 'FIFO Ice Cream',
|
||||
})
|
||||
|
||||
# I create a draft Purchase Order for first in move for 10 kg at 50 euro
|
||||
purchase_order_1 = self.env['purchase.order'].create({
|
||||
'partner_id': self.env.ref('base.res_partner_3').id,
|
||||
'order_line': [(0, 0, {
|
||||
'name': 'FIFO Ice Cream',
|
||||
'product_id': product_icecream.id,
|
||||
'product_qty': 10.0,
|
||||
'product_uom': self.env.ref('product.product_uom_kgm').id,
|
||||
'price_unit': 50.0,
|
||||
'date_planned': time.strftime('%Y-%m-%d')})],
|
||||
})
|
||||
|
||||
# Confirm the first purchase order
|
||||
purchase_order_1.button_confirm()
|
||||
|
||||
# Check the "Purchase" status of purchase order 1
|
||||
self.assertEquals(purchase_order_1.state, 'purchase')
|
||||
|
||||
# Process the reception of purchase order 1 and set date
|
||||
picking = purchase_order_1.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
|
||||
# Check the standard price of the product (fifo icecream), that should have not changed
|
||||
# because the standard price is supposed to be updated only when goods are going out of the stock
|
||||
self.assertEquals(product_icecream.standard_price, 70.0, 'Standard price should not have changed')
|
||||
self.assertEquals(product_icecream.stock_value, 500.0, 'Wrong stock value')
|
||||
|
||||
# I create a draft Purchase Order for second shipment for 30 kg at 80 euro
|
||||
purchase_order_2 = self.env['purchase.order'].create({
|
||||
'partner_id': self.env.ref('base.res_partner_3').id,
|
||||
'order_line': [(0, 0, {
|
||||
'name': 'FIFO Ice Cream',
|
||||
'product_id': product_icecream.id,
|
||||
'product_qty': 30.0,
|
||||
'product_uom': self.env.ref('product.product_uom_kgm').id,
|
||||
'price_unit': 80.0,
|
||||
'date_planned': time.strftime('%Y-%m-%d')})],
|
||||
})
|
||||
|
||||
# Confirm the second purchase order
|
||||
purchase_order_2.button_confirm()
|
||||
|
||||
# Process the reception of purchase order 2
|
||||
picking = purchase_order_2.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
|
||||
# Check the standard price of the product, that should have not changed because the
|
||||
# standard price is supposed to be updated only when goods are going out of the stock
|
||||
self.assertEquals(product_icecream.standard_price, 70.0, 'Standard price as fifo price of second reception incorrect!')
|
||||
self.assertEquals(product_icecream.stock_value, 2900.0, 'Stock valuation should be 2900')
|
||||
|
||||
# Let us send some goods
|
||||
outgoing_shipment = self.env['stock.picking'].create({
|
||||
'picking_type_id': self.env.ref('stock.picking_type_out').id,
|
||||
'location_id': self.env.ref('stock.stock_location_stock').id,
|
||||
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
|
||||
'move_lines': [(0, 0, {
|
||||
'name': product_icecream.name,
|
||||
'product_id': product_icecream.id,
|
||||
'product_uom_qty': 20.0,
|
||||
'product_uom': self.env.ref('product.product_uom_kgm').id,
|
||||
'location_id': self.env.ref('stock.stock_location_stock').id,
|
||||
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
|
||||
'picking_type_id': self.env.ref('stock.picking_type_out').id})]
|
||||
})
|
||||
|
||||
# I assign this outgoing shipment
|
||||
outgoing_shipment.action_assign()
|
||||
|
||||
# Process the delivery of the outgoing shipment
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, outgoing_shipment.id)]}).process()
|
||||
|
||||
# Check stock value became 1600 .
|
||||
self.assertEqual(product_icecream.stock_value, 1600.0, 'Stock valuation should be 1600')
|
||||
|
||||
# Do a delivery of an extra 500 g (delivery order)
|
||||
outgoing_shipment_uom = self.env['stock.picking'].create({
|
||||
'picking_type_id': self.env.ref('stock.picking_type_out').id,
|
||||
'location_id': self.env.ref('stock.stock_location_stock').id,
|
||||
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
|
||||
'move_lines': [(0, 0, {
|
||||
'name': product_icecream.name,
|
||||
'product_id': product_icecream.id,
|
||||
'product_uom_qty': 500.0,
|
||||
'product_uom': self.env.ref('product.product_uom_gram').id,
|
||||
'location_id': self.env.ref('stock.stock_location_stock').id,
|
||||
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
|
||||
'picking_type_id': self.env.ref('stock.picking_type_out').id})]
|
||||
})
|
||||
|
||||
# I assign this outgoing shipment
|
||||
outgoing_shipment_uom.action_assign()
|
||||
|
||||
# Process the delivery of the outgoing shipment
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, outgoing_shipment_uom.id)]}).process()
|
||||
|
||||
# Check stock valuation and qty in stock
|
||||
self.assertEqual(product_icecream.stock_value, 1560.0, 'Stock valuation should be 1560')
|
||||
self.assertEqual(product_icecream.qty_available, 19.5, 'Should still have 19.5 in stock')
|
||||
|
||||
# We will temporarily change the currency rate on the sixth of June to have the same results all year
|
||||
NewUSD = self.env['res.currency'].create({
|
||||
'name': 'new_usd',
|
||||
'symbol': '$²',
|
||||
'rate_ids': [(0, 0, {'rate': 1.2834, 'name': time.strftime('%Y-%m-%d')})],
|
||||
})
|
||||
|
||||
# Create PO for 30000 g at 0.150$/g and 10 kg at 150$/kg
|
||||
purchase_order_usd = self.env['purchase.order'].create({
|
||||
'partner_id': self.env.ref('base.res_partner_3').id,
|
||||
'currency_id': NewUSD.id,
|
||||
'order_line': [(0, 0, {
|
||||
'name': 'FIFO Ice Cream',
|
||||
'product_id': product_icecream.id,
|
||||
'product_qty': 30,
|
||||
'product_uom': self.env.ref('product.product_uom_kgm').id,
|
||||
'price_unit': 0.150,
|
||||
'date_planned': time.strftime('%Y-%m-%d')}),
|
||||
(0, 0, {
|
||||
'name': product_icecream.name,
|
||||
'product_id': product_icecream.id,
|
||||
'product_qty': 10.0,
|
||||
'product_uom': self.env.ref('product.product_uom_kgm').id,
|
||||
'price_unit': 150.0,
|
||||
'date_planned': time.strftime('%Y-%m-%d')})]
|
||||
})
|
||||
|
||||
# Confirm the purchase order in USD
|
||||
purchase_order_usd.button_confirm()
|
||||
# Process the reception of purchase order with USD
|
||||
picking = purchase_order_usd.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
|
||||
# Create delivery order of 49.5 kg
|
||||
outgoing_shipment_cur = self.env['stock.picking'].create({
|
||||
'picking_type_id': self.env.ref('stock.picking_type_out').id,
|
||||
'location_id': self.env.ref('stock.stock_location_stock').id,
|
||||
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
|
||||
'move_lines': [(0, 0, {
|
||||
'name': product_icecream.name,
|
||||
'product_id': product_icecream.id,
|
||||
'product_uom_qty': 49.5,
|
||||
'product_uom': self.env.ref('product.product_uom_kgm').id,
|
||||
'location_id': self.env.ref('stock.stock_location_stock').id,
|
||||
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
|
||||
'picking_type_id': self.env.ref('stock.picking_type_out').id})]
|
||||
})
|
||||
|
||||
# I assign this outgoing shipment
|
||||
outgoing_shipment_cur.action_assign()
|
||||
|
||||
# Process the delivery of the outgoing shipment
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, outgoing_shipment_cur.id)]}).process()
|
||||
|
||||
# Do a delivery of an extra 10 kg
|
||||
outgoing_shipment_ret = self.env['stock.picking'].create({
|
||||
'picking_type_id': self.env.ref('stock.picking_type_out').id,
|
||||
'location_id': self.env.ref('stock.stock_location_stock').id,
|
||||
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
|
||||
'move_lines': [(0, 0, {
|
||||
'name': product_icecream.name,
|
||||
'product_id': product_icecream.id,
|
||||
'product_uom_qty': 10,
|
||||
'product_uom': self.env.ref('product.product_uom_kgm').id,
|
||||
'location_id': self.env.ref('stock.stock_location_stock').id,
|
||||
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
|
||||
'picking_type_id': self.env.ref('stock.picking_type_out').id})]
|
||||
})
|
||||
|
||||
# I assign this outgoing shipment
|
||||
outgoing_shipment_ret.action_assign()
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, outgoing_shipment_ret.id)]}).process()
|
||||
|
||||
# Check rounded price is 150.0 / 1.2834
|
||||
self.assertEqual(round(product_icecream.qty_available), 0.0, 'Wrong quantity in stock after first reception.')
|
||||
|
||||
# Let us create some outs to get negative stock for a new product using the same config
|
||||
product_fifo_negative = self.env['product.product'].create({
|
||||
'default_code': 'NEG',
|
||||
'name': 'FIFO Negative',
|
||||
'type': 'product',
|
||||
'categ_id': self.env.ref('product.product_category_1').id,
|
||||
'list_price': 100.0,
|
||||
'standard_price': 70.0,
|
||||
'uom_id': self.env.ref('product.product_uom_kgm').id,
|
||||
'uom_po_id': self.env.ref('product.product_uom_kgm').id,
|
||||
'cost_method': 'fifo',
|
||||
'valuation': 'real_time',
|
||||
'property_stock_account_input': self.ref('purchase.o_expense'),
|
||||
'property_stock_account_output': self.ref('purchase.o_income'),
|
||||
'supplier_taxes_id': '[]',
|
||||
'description': 'FIFO Ice Cream',
|
||||
})
|
||||
|
||||
# Create outpicking.create delivery order of 100 kg.
|
||||
outgoing_shipment_neg = self.env['stock.picking'].create({
|
||||
'picking_type_id': self.env.ref('stock.picking_type_out').id,
|
||||
'location_id': self.env.ref('stock.stock_location_stock').id,
|
||||
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
|
||||
'move_lines': [(0, 0, {
|
||||
'name': product_fifo_negative.name,
|
||||
'product_id': product_fifo_negative.id,
|
||||
'product_uom_qty': 100,
|
||||
'product_uom': self.env.ref('product.product_uom_kgm').id,
|
||||
'location_id': self.env.ref('stock.stock_location_stock').id,
|
||||
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
|
||||
'picking_type_id': self.env.ref('stock.picking_type_out').id})]
|
||||
})
|
||||
|
||||
# Process the delivery of the first outgoing shipment
|
||||
outgoing_shipment_neg.action_confirm()
|
||||
outgoing_shipment_neg.move_lines[0].quantity_done = 100.0
|
||||
outgoing_shipment_neg.action_done()
|
||||
|
||||
# Check qty available = -100
|
||||
self.assertEqual(product_fifo_negative.qty_available, -100, 'Stock qty should be -100')
|
||||
|
||||
# The behavior of fifo/lifo is not garantee if the quants are created at the same second, so just wait one second
|
||||
time.sleep(1)
|
||||
|
||||
# Let create another out shipment of 400 kg
|
||||
outgoing_shipment_neg2 = self.env['stock.picking'].create({
|
||||
'picking_type_id': self.env.ref('stock.picking_type_out').id,
|
||||
'location_id': self.env.ref('stock.stock_location_stock').id,
|
||||
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
|
||||
'move_lines': [(0, 0, {
|
||||
'name': product_fifo_negative.name,
|
||||
'product_id': product_fifo_negative.id,
|
||||
'product_uom_qty': 400,
|
||||
'product_uom': self.env.ref('product.product_uom_kgm').id,
|
||||
'location_id': self.env.ref('stock.stock_location_stock').id,
|
||||
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
|
||||
'picking_type_id': self.env.ref('stock.picking_type_out').id})]
|
||||
})
|
||||
|
||||
# Process the delivery of the outgoing shipments
|
||||
outgoing_shipment_neg2.action_confirm()
|
||||
outgoing_shipment_neg2.move_lines[0].quantity_done = 400.0
|
||||
outgoing_shipment_neg2.action_done()
|
||||
|
||||
# Check qty available = -500
|
||||
self.assertEqual(product_fifo_negative.qty_available, -500, 'Stock qty should be -500')
|
||||
|
||||
# Receive purchase order with 50 kg Ice Cream at 50€/kg
|
||||
purchase_order_neg = self.env['purchase.order'].create({
|
||||
'partner_id': self.env.ref('base.res_partner_3').id,
|
||||
'order_line': [(0, 0, {
|
||||
'name': 'FIFO Ice Cream',
|
||||
'product_id': product_fifo_negative.id,
|
||||
'product_qty': 50.0,
|
||||
'product_uom': self.env.ref('product.product_uom_kgm').id,
|
||||
'price_unit': 50.0,
|
||||
'date_planned': time.strftime('%Y-%m-%d')})],
|
||||
})
|
||||
|
||||
# I confirm the first purchase order
|
||||
purchase_order_neg.button_confirm()
|
||||
|
||||
# Process the reception of purchase order neg
|
||||
picking = purchase_order_neg.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
|
||||
# Receive purchase order with 600 kg FIFO Ice Cream at 80 euro/kg
|
||||
purchase_order_neg2 = self.env['purchase.order'].create({
|
||||
'partner_id': self.env.ref('base.res_partner_3').id,
|
||||
'order_line': [(0, 0, {
|
||||
'name': product_icecream.name,
|
||||
'product_id': product_fifo_negative.id,
|
||||
'product_qty': 600.0,
|
||||
'product_uom': self.env.ref('product.product_uom_kgm').id,
|
||||
'price_unit': 80.0,
|
||||
'date_planned': time.strftime('%Y-%m-%d')})],
|
||||
})
|
||||
|
||||
# I confirm the second negative purchase order
|
||||
purchase_order_neg2.button_confirm()
|
||||
|
||||
# Process the reception of purchase order neg2
|
||||
picking = purchase_order_neg2.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
|
||||
original_out_move = outgoing_shipment_neg.move_lines[0]
|
||||
outgoing_shipment_fifo_icecream_neg2 = outgoing_shipment_neg2.move_lines[0]
|
||||
original_out_move._fifo_vacuum()
|
||||
outgoing_shipment_fifo_icecream_neg2._fifo_vacuum()
|
||||
self.assertEquals(original_out_move.product_id.stock_value, 12000.0, 'Value of the move should be 2500')
|
||||
self.assertEquals(original_out_move.product_id.qty_available, 150.0, 'Qty available should be 150')
|
||||
@@ -0,0 +1,87 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
|
||||
from .common import TestPurchase
|
||||
|
||||
|
||||
class TestFifoReturns(TestPurchase):
|
||||
|
||||
def test_fifo_returns(self):
|
||||
"""Test to create product and purchase order to test the FIFO returns of the product"""
|
||||
|
||||
self._load('account', 'test', 'account_minimal_test.xml')
|
||||
self._load('stock_account', 'test', 'stock_valuation_account.xml')
|
||||
|
||||
# Set a product as using fifo price
|
||||
product_fiforet_icecream = self.env['product.product'].create({
|
||||
'default_code': 'FIFORET',
|
||||
'name': 'FIFO Ice Cream',
|
||||
'type': 'product',
|
||||
'categ_id': self.env.ref('product.product_category_1').id,
|
||||
'standard_price': 0.0,
|
||||
'uom_id': self.env.ref('product.product_uom_kgm').id,
|
||||
'uom_po_id': self.env.ref('product.product_uom_kgm').id,
|
||||
'cost_method': 'fifo',
|
||||
'valuation': 'real_time',
|
||||
'property_stock_account_input': self.ref('purchase.o_expense'),
|
||||
'property_stock_account_output': self.ref('purchase.o_income'),
|
||||
'description': 'FIFO Ice Cream',
|
||||
})
|
||||
|
||||
# I create a draft Purchase Order for first in move for 10 kg at 50 euro
|
||||
purchase_order_1 = self.env['purchase.order'].create({
|
||||
'partner_id': self.ref('base.res_partner_3'),
|
||||
'order_line': [(0, 0, {
|
||||
'name': 'FIFO Ice Cream',
|
||||
'product_id': product_fiforet_icecream.id,
|
||||
'product_qty': 10.0,
|
||||
'product_uom': self.env.ref('product.product_uom_kgm').id,
|
||||
'price_unit': 50.0,
|
||||
'date_planned': time.strftime('%Y-%m-%d'),
|
||||
})],
|
||||
})
|
||||
|
||||
# Create a draft Purchase Order for second shipment for 30kg at 80€/kg
|
||||
purchase_order_2 = self.env['purchase.order'].create({
|
||||
'partner_id': self.ref('base.res_partner_3'),
|
||||
'order_line': [(0, 0, {
|
||||
'name': 'FIFO Ice Cream',
|
||||
'product_id': product_fiforet_icecream.id,
|
||||
'product_qty': 30.0,
|
||||
'product_uom': self.env.ref('product.product_uom_kgm').id,
|
||||
'price_unit': 80.0,
|
||||
'date_planned': time.strftime('%Y-%m-%d'),
|
||||
})],
|
||||
})
|
||||
|
||||
# Confirm the first purchase order
|
||||
purchase_order_1.button_confirm()
|
||||
|
||||
# Process the reception of purchase order 1
|
||||
picking = purchase_order_1.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
|
||||
# Check the standard price of the product (fifo icecream)
|
||||
self.assertEqual(product_fiforet_icecream.standard_price, 0.0, 'Standard price should not have changed!')
|
||||
|
||||
# Confirm the second purchase order
|
||||
purchase_order_2.button_confirm()
|
||||
picking = purchase_order_2.picking_ids[0]
|
||||
self.env['stock.immediate.transfer'].create({'pick_ids': [(4, picking.id)]}).process()
|
||||
|
||||
# Return the goods of purchase order 2
|
||||
picking = purchase_order_2.picking_ids[0]
|
||||
return_pick_wiz = self.env['stock.return.picking'].with_context(
|
||||
active_model='stock.picking', active_id=picking.id).create({})
|
||||
return_picking_id, dummy = return_pick_wiz.with_context(active_id=picking.id)._create_returns()
|
||||
|
||||
# Important to pass through confirmation and assignation
|
||||
return_picking = self.env['stock.picking'].browse(return_picking_id)
|
||||
return_picking.action_confirm()
|
||||
return_picking.move_lines[0].quantity_done = return_picking.move_lines[0].product_uom_qty
|
||||
return_picking.action_done()
|
||||
|
||||
# After the return only 10 of the second purchase order should still be in stock as it applies fifo on the return too
|
||||
self.assertEqual(product_fiforet_icecream.qty_available, 10.0, 'Qty available should be 10.0')
|
||||
self.assertEqual(product_fiforet_icecream.stock_value, 800.0, 'Stock value should be 800')
|
||||
@@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from .common import TestPurchase
|
||||
|
||||
|
||||
class TestDeleteOrder(TestPurchase):
|
||||
|
||||
def test_00_delete_order(self):
|
||||
''' Testcase for deleting purchase order with purchase user group'''
|
||||
|
||||
# In order to test delete process on purchase order,tried to delete a confirmed order and check Error Message.
|
||||
purchase_order_1 = self.env.ref('purchase.purchase_order_1').sudo(self.res_users_purchase_user.id)
|
||||
with self.assertRaises(UserError):
|
||||
purchase_order_1.unlink()
|
||||
|
||||
# Delete 'cancelled' purchase order with user group
|
||||
purchase_order_7 = self.env.ref('purchase.purchase_order_7').sudo(self.res_users_purchase_user.id)
|
||||
purchase_order_7.button_cancel()
|
||||
self.assertEqual(purchase_order_7.state, 'cancel', 'PO is cancelled!')
|
||||
purchase_order_7.unlink()
|
||||
|
||||
# Delete 'draft' purchase order with user group
|
||||
purchase_order_5 = self.env.ref('purchase.purchase_order_5').sudo(self.res_users_purchase_user.id)
|
||||
self.assertEqual(purchase_order_5.state, 'draft', 'PO in draft state!')
|
||||
purchase_order_5.button_cancel()
|
||||
self.assertEqual(purchase_order_5.state, 'cancel', 'PO is cancelled!')
|
||||
purchase_order_5.unlink()
|
||||
@@ -0,0 +1,25 @@
|
||||
from .common import TestPurchase
|
||||
|
||||
|
||||
class TestPurchaseOrderProcess(TestPurchase):
|
||||
|
||||
def test_00_cancel_purchase_order_flow(self):
|
||||
""" Test cancel purchase order with group user."""
|
||||
|
||||
# In order to test the cancel flow,start it from canceling confirmed purchase order.
|
||||
po_edit_with_user = self.env.ref('purchase.purchase_order_5').sudo(self.res_users_purchase_user.id)
|
||||
|
||||
# Confirm the purchase order.
|
||||
po_edit_with_user.button_confirm()
|
||||
|
||||
# Check the "Approved" status after confirmed RFQ.
|
||||
self.assertEqual(po_edit_with_user.state, 'purchase', 'Purchase: PO state should be "Purchase')
|
||||
|
||||
# First cancel receptions related to this order if order shipped.
|
||||
po_edit_with_user.picking_ids.action_cancel()
|
||||
|
||||
# Able to cancel purchase order.
|
||||
po_edit_with_user.button_cancel()
|
||||
|
||||
# Check that order is cancelled.
|
||||
self.assertEqual(po_edit_with_user.state, 'cancel', 'Purchase: PO state should be "Cancel')
|
||||
@@ -11,14 +11,13 @@
|
||||
'category': 'Warehouse',
|
||||
'sequence': 13,
|
||||
'demo': [
|
||||
'data/stock_demo_pre.yml',
|
||||
'data/stock_demo_pre.xml',
|
||||
'data/procurement_demo.xml',
|
||||
'data/stock_demo.xml',
|
||||
'data/stock_orderpoint_demo.xml',
|
||||
'data/stock_orderpoint_demo.yml',
|
||||
'data/stock_demo.yml',
|
||||
'data/stock_demo2.xml',
|
||||
'data/stock_location_demo_cpu1.xml',
|
||||
'data/stock_location_demo_cpu3.yml',
|
||||
'data/stock_location_demo_cpu3.xml',
|
||||
'data/stock_quant_demo.xml',
|
||||
],
|
||||
'data': [
|
||||
@@ -66,20 +65,12 @@
|
||||
|
||||
'data/default_barcode_patterns.xml',
|
||||
'data/stock_data.xml',
|
||||
'data/stock_data.yml',
|
||||
'data/stock_incoterms_data.xml',
|
||||
'data/stock_sequence_data.xml',
|
||||
],
|
||||
'qweb': [
|
||||
'static/src/xml/stock_traceability_report_backend.xml',
|
||||
],
|
||||
'test': [
|
||||
'test/stock_users.yml',
|
||||
'test/packing.yml',
|
||||
'test/packingneg.yml',
|
||||
'test/procrule.yml',
|
||||
'test/wiseoperator.yml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'auto_install': False,
|
||||
|
||||
@@ -143,5 +143,109 @@
|
||||
<field name="number_increment">1</field>
|
||||
</record>
|
||||
|
||||
<record id="base.main_partner" model="res.partner">
|
||||
<field name="property_stock_customer" model="stock.warehouse"
|
||||
eval="obj().env.ref('stock.warehouse0').lot_stock_id.id"/>
|
||||
</record>
|
||||
|
||||
<!-- create xml ids for demo data that are widely used in tests or in other codes, for more convenience -->
|
||||
<function model="ir.model.data" name="create">
|
||||
<value model="stock.warehouse" eval="{
|
||||
'name': 'stock_location_stock',
|
||||
'module':'stock',
|
||||
'model': 'stock.location',
|
||||
'res_id': obj().env.ref('stock.warehouse0').lot_stock_id.id,
|
||||
}"/>
|
||||
</function>
|
||||
<function model="ir.model.data" name="create">
|
||||
<value model="stock.warehouse" eval="{
|
||||
'name': 'stock_location_company',
|
||||
'module':'stock',
|
||||
'model': 'stock.location',
|
||||
'res_id': obj().env.ref('stock.warehouse0').wh_input_stock_loc_id.id,
|
||||
}"/>
|
||||
</function>
|
||||
<function model="ir.model.data" name="create">
|
||||
<value model="stock.warehouse" eval="{
|
||||
'name': 'stock_location_output',
|
||||
'module':'stock',
|
||||
'model': 'stock.location',
|
||||
'res_id': obj().env.ref('stock.warehouse0').wh_output_stock_loc_id.id,
|
||||
}"/>
|
||||
</function>
|
||||
<function model="ir.model.data" name="create">
|
||||
<value model="stock.warehouse" eval="{
|
||||
'name': 'location_pack_zone',
|
||||
'module':'stock',
|
||||
'model': 'stock.location',
|
||||
'res_id': obj().env.ref('stock.warehouse0').wh_pack_stock_loc_id.id,
|
||||
}"/>
|
||||
</function>
|
||||
<function model="ir.model.data" name="create">
|
||||
<value model="stock.warehouse" eval="{
|
||||
'name': 'picking_type_internal',
|
||||
'module':'stock',
|
||||
'model': 'stock.picking.type',
|
||||
'res_id': obj().env.ref('stock.warehouse0').int_type_id.id,
|
||||
}"/>
|
||||
</function>
|
||||
<function model="ir.model.data" name="create">
|
||||
<value model="stock.warehouse" eval="{
|
||||
'name': 'picking_type_in',
|
||||
'module':'stock',
|
||||
'model': 'stock.picking.type',
|
||||
'res_id': obj().env.ref('stock.warehouse0').in_type_id.id,
|
||||
}"/>
|
||||
</function>
|
||||
<function model="ir.model.data" name="create">
|
||||
<value model="stock.warehouse" eval="{
|
||||
'name': 'picking_type_out',
|
||||
'module':'stock',
|
||||
'model': 'stock.picking.type',
|
||||
'res_id': obj().env.ref('stock.warehouse0').out_type_id.id,
|
||||
}"/>
|
||||
</function>
|
||||
|
||||
<!-- avoid the xml id and the associated resource being dropped by the orm by manually making a hit on it -->
|
||||
<function model="ir.model.data" name="_update_dummy">
|
||||
<value>stock.location</value>
|
||||
<value>stock</value>
|
||||
<value>stock_location_stock</value>
|
||||
</function>
|
||||
<function model="ir.model.data" name="_update_dummy">
|
||||
<value>stock.location</value>
|
||||
<value>stock</value>
|
||||
<value>stock_location_company</value>
|
||||
</function>
|
||||
<function model="ir.model.data" name="_update_dummy">
|
||||
<value>stock.location</value>
|
||||
<value>stock</value>
|
||||
<value>stock_location_output</value>
|
||||
</function>
|
||||
<function model="ir.model.data" name="_update_dummy">
|
||||
<value>stock.location</value>
|
||||
<value>stock</value>
|
||||
<value>location_pack_zone</value>
|
||||
</function>
|
||||
<function model="ir.model.data" name="_update_dummy">
|
||||
<value>stock.picking.type</value>
|
||||
<value>stock</value>
|
||||
<value>picking_type_internal</value>
|
||||
</function>
|
||||
<function model="ir.model.data" name="_update_dummy">
|
||||
<value>stock.picking.type</value>
|
||||
<value>stock</value>
|
||||
<value>picking_type_in</value>
|
||||
</function>
|
||||
<function model="ir.model.data" name="_update_dummy">
|
||||
<value>stock.picking.type</value>
|
||||
<value>stock</value>
|
||||
<value>picking_type_out</value>
|
||||
</function>
|
||||
|
||||
<!-- create the transit location for each company existing -->
|
||||
<function model="res.company" name="create_transit_location">
|
||||
<value model="res.company" eval="obj().search([('internal_transit_location_id', '=', False)]).ids"/>
|
||||
</function>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
-
|
||||
!python {model: res.partner, id: base.main_partner}: |
|
||||
main_warehouse = self.env['stock.warehouse'].browse(ref('warehouse0'))
|
||||
self.write({'property_stock_customer':main_warehouse.lot_stock_id.id})
|
||||
|
||||
-
|
||||
!python {model: ir.model.data, id: False}: |
|
||||
main_warehouse = self.env['stock.warehouse'].browse(ref('warehouse0'))
|
||||
|
||||
#create xml ids for demo data that are widely used in tests or in other codes, for more convenience
|
||||
xml_references = [
|
||||
{'name': 'stock_location_stock', 'module': 'stock', 'model': 'stock.location', 'res_id': main_warehouse.lot_stock_id.id},
|
||||
{'name': 'stock_location_company', 'module': 'stock', 'model': 'stock.location', 'res_id': main_warehouse.wh_input_stock_loc_id.id},
|
||||
{'name':'stock_location_output','module':'stock', 'model':'stock.location','res_id':main_warehouse.wh_output_stock_loc_id.id},
|
||||
{'name':'location_pack_zone','module':'stock', 'model':'stock.location','res_id':main_warehouse.wh_pack_stock_loc_id.id},
|
||||
{'name':'picking_type_internal','module':'stock', 'model':'stock.picking.type','res_id':main_warehouse.int_type_id.id},
|
||||
{'name':'picking_type_in','module':'stock', 'model':'stock.picking.type','res_id':main_warehouse.in_type_id.id},
|
||||
{'name':'picking_type_out','module':'stock', 'model':'stock.picking.type','res_id':main_warehouse.out_type_id.id},
|
||||
]
|
||||
for xml_record in xml_references:
|
||||
xml_ids = self.search([('module', '=', xml_record['module']), ('model', '=', xml_record['model']), ('name', '=', xml_record['name'])]).ids
|
||||
if not xml_ids:
|
||||
self.create(xml_record)
|
||||
#avoid the xml id and the associated resource being dropped by the orm by manually making a hit on it
|
||||
self._update_dummy(xml_record['model'], xml_record['module'], xml_record['name'])
|
||||
|
||||
-
|
||||
!python {model: res.company, id: False}: |
|
||||
#create the transit location for each company existing
|
||||
companies = self.search([('internal_transit_location_id', '=', False)])
|
||||
for company in companies:
|
||||
company.create_transit_location()
|
||||
|
||||
@@ -1,354 +0,0 @@
|
||||
-
|
||||
!python {model: ir.model.data, id: False}: |
|
||||
chicago_warehouse = self.env['stock.warehouse'].browse(ref('stock_warehouse_shop0'))
|
||||
|
||||
#create xml ids for demo data that are widely used in tests or in other codes, for more convenience
|
||||
xml_references = [
|
||||
{'name':'chi_picking_type_in','module':'stock', 'model':'stock.picking.type','res_id': chicago_warehouse.in_type_id.id},
|
||||
{'name':'chi_picking_type_out','module':'stock', 'model':'stock.picking.type','res_id': chicago_warehouse.out_type_id.id},
|
||||
{'name':'stock_location_shop0','module':'stock', 'model':'stock.location','res_id': chicago_warehouse.lot_stock_id.id},
|
||||
]
|
||||
for xml_record in xml_references:
|
||||
xml_ids = self.search([('module', '=', xml_record['module']), ('model', '=', xml_record['model']), ('name', '=', xml_record['name'])]).ids
|
||||
if not xml_ids:
|
||||
self.create(xml_record)
|
||||
#avoid the xml id and the associated resource being dropped by the orm by manually making a hit on it
|
||||
self._update_dummy(xml_record['model'], xml_record['module'], xml_record['name'])
|
||||
-
|
||||
!record {model: stock.location, id: location_refrigerator_small}:
|
||||
name: Small Refrigerator
|
||||
usage: internal
|
||||
location_id: stock_location_14
|
||||
-
|
||||
!record {model: product.product, id: product_icecream}:
|
||||
default_code: 001
|
||||
name: Ice Cream
|
||||
type: product
|
||||
categ_id: product.product_category_1
|
||||
lst_price: 100.0
|
||||
standard_price: 70.0
|
||||
weight: 1.0
|
||||
uom_id: product.product_uom_kgm
|
||||
property_stock_inventory: location_inventory
|
||||
description: Ice cream can be mass-produced and thus is widely available in developed parts of the world. Ice cream can be purchased in large cartons (vats and squrounds) from supermarkets and grocery stores, in smaller quantities from ice cream shops, convenience stores, and milk bars, and in individual servings from small carts or vans at public events.
|
||||
-
|
||||
!record {model: stock.production.lot, id: lot_icecream_0}:
|
||||
name: '0000000000017'
|
||||
product_id: product_icecream
|
||||
-
|
||||
!record {model: stock.production.lot, id: lot_icecream_1}:
|
||||
name: '0000000000024'
|
||||
product_id: product_icecream
|
||||
-
|
||||
!record {model: stock.inventory, id: stock_inventory_icecream}:
|
||||
name: Inventory for icecream
|
||||
filter: 'product'
|
||||
product_id: product_icecream
|
||||
-
|
||||
!record {model: stock.inventory.line, id: stock_inventory_line_icecream_lot0}:
|
||||
product_id: product_icecream
|
||||
product_uom_id: product.product_uom_kgm
|
||||
inventory_id: stock_inventory_icecream
|
||||
product_qty: 50.0
|
||||
prod_lot_id: lot_icecream_0
|
||||
location_id: stock_location_14
|
||||
-
|
||||
!record {model: stock.inventory.line, id: stock_inventory_line_icecream_lot1}:
|
||||
product_id: product_icecream
|
||||
product_uom_id: product.product_uom_kgm
|
||||
inventory_id: stock_inventory_icecream
|
||||
product_qty: 40.0
|
||||
prod_lot_id: lot_icecream_1
|
||||
location_id: stock_location_14
|
||||
-
|
||||
Create STOCK_MOVE for OUT
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_shipment_main_warehouse}:
|
||||
picking_type_id: stock.picking_type_out
|
||||
origin: 'outgoing shipment main_warehouse'
|
||||
partner_id: base.res_partner_1
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
move_lines:
|
||||
- product_id: product.product_product_27
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 15.0
|
||||
picking_type_id: stock.picking_type_out
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_shipment_main_warehouse1}:
|
||||
picking_type_id: stock.picking_type_out
|
||||
origin: 'outgoing shipment'
|
||||
partner_id: base.res_partner_1
|
||||
date: !eval "'%s-%s-15' %((datetime.now()-timedelta(days=60)).year, (datetime.now()-timedelta(days=60)).month)"
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
move_lines:
|
||||
- product_id: product.product_product_6
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 180.0
|
||||
picking_type_id: stock.picking_type_out
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
move_line_ids:
|
||||
- product_id: product.product_product_6
|
||||
qty_done: 100
|
||||
product_uom_id: product.product_uom_unit
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_shipment_main_warehouse2}:
|
||||
picking_type_id: stock.picking_type_out
|
||||
origin: 'outgoing shipment your_company warehouse'
|
||||
partner_id: base.res_partner_1
|
||||
date: !eval "'%s-%s-15' % ((datetime.now()-timedelta(days=30)).month,(datetime.now()-timedelta(days=30)).month)"
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
move_lines:
|
||||
- product_id: product.product_delivery_02
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 45.0
|
||||
picking_type_id: stock.picking_type_out
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_shipment_main_warehouse3}:
|
||||
picking_type_id: stock.picking_type_out
|
||||
origin: 'your company warehouse'
|
||||
partner_id: base.res_partner_1
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
move_lines:
|
||||
- product_id: product.product_product_5
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 75.0
|
||||
picking_type_id: stock.picking_type_out
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
-
|
||||
Create STOCK_PICKING for IN
|
||||
-
|
||||
!record {model: stock.picking, id: incomming_shipment}:
|
||||
picking_type_id: stock.picking_type_in
|
||||
location_id: stock_location_suppliers
|
||||
location_dest_id: stock_location_stock
|
||||
-
|
||||
!record {model: stock.move, id: incomming_shipment_icecream}:
|
||||
product_id: product_icecream
|
||||
product_uom: product.product_uom_kgm
|
||||
product_uom_qty: 50.0
|
||||
picking_type_id: stock.picking_type_in
|
||||
location_id: stock_location_suppliers
|
||||
location_dest_id: stock_location_stock
|
||||
picking_id: incomming_shipment
|
||||
-
|
||||
!record {model: stock.picking, id: incomming_shipment1}:
|
||||
picking_type_id: stock.picking_type_in
|
||||
partner_id: base.res_partner_1
|
||||
location_id: stock.stock_location_suppliers
|
||||
location_dest_id: stock.stock_location_stock
|
||||
move_lines:
|
||||
- product_id: product.product_delivery_01
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 35.0
|
||||
picking_type_id: stock.picking_type_in
|
||||
location_id: stock.stock_location_suppliers
|
||||
location_dest_id: stock.stock_location_stock
|
||||
-
|
||||
!record {model: stock.picking, id: incomming_shipment2}:
|
||||
picking_type_id: stock.picking_type_in
|
||||
partner_id: base.res_partner_1
|
||||
location_id: stock.stock_location_suppliers
|
||||
location_dest_id: stock.stock_location_stock
|
||||
move_lines:
|
||||
- product_id: product.product_product_22
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 125.0
|
||||
picking_type_id: stock.picking_type_in
|
||||
location_id: stock.stock_location_suppliers
|
||||
location_dest_id: stock.stock_location_stock
|
||||
-
|
||||
!record {model: stock.picking, id: incomming_shipment3}:
|
||||
picking_type_id: stock.picking_type_in
|
||||
partner_id: base.res_partner_1
|
||||
location_id: stock.stock_location_suppliers
|
||||
location_dest_id: stock.stock_location_stock
|
||||
move_lines:
|
||||
- product_id: product.product_product_10
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 120.0
|
||||
picking_type_id: stock.picking_type_in
|
||||
location_id: stock.stock_location_suppliers
|
||||
location_dest_id: stock.stock_location_stock
|
||||
-
|
||||
!record {model: stock.picking, id: incomming_shipment4}:
|
||||
picking_type_id: stock.picking_type_in
|
||||
partner_id: base.res_partner_1
|
||||
location_id: stock.stock_location_suppliers
|
||||
location_dest_id: stock.stock_location_stock
|
||||
move_lines:
|
||||
- product_id: product.product_delivery_02
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 50.0
|
||||
picking_type_id: stock.picking_type_in
|
||||
location_id: stock.stock_location_suppliers
|
||||
location_dest_id: stock.stock_location_stock
|
||||
-
|
||||
Create STOCK_PICKING_IN for Chicago Warehouse
|
||||
-
|
||||
!record {model: stock.picking, id: incomming_chicago_warehouse}:
|
||||
picking_type_id: chi_picking_type_in
|
||||
origin: 'incoming_chicago_warehouse'
|
||||
partner_id: base.res_partner_1
|
||||
location_id: stock.stock_location_suppliers
|
||||
location_dest_id: stock.stock_location_shop0
|
||||
move_lines:
|
||||
- product_id: product.product_product_9
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 25.0
|
||||
picking_type_id: chi_picking_type_in
|
||||
location_id: stock.stock_location_suppliers
|
||||
location_dest_id: stock.stock_location_shop0
|
||||
|
||||
-
|
||||
!record {model: stock.picking, id: incomming_chicago_warehouse1}:
|
||||
picking_type_id: chi_picking_type_in
|
||||
partner_id: base.res_partner_1
|
||||
location_id: stock.stock_location_suppliers
|
||||
location_dest_id: stock.stock_location_shop0
|
||||
move_lines:
|
||||
- product_id: product.product_delivery_01
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 180.0
|
||||
picking_type_id: chi_picking_type_in
|
||||
location_id: stock.stock_location_suppliers
|
||||
location_dest_id: stock.stock_location_shop0
|
||||
state: done
|
||||
move_line_ids:
|
||||
- product_id: product.product_delivery_01
|
||||
qty_done: 100
|
||||
product_uom_id: product.product_uom_unit
|
||||
location_id: stock.stock_location_suppliers
|
||||
location_dest_id: stock.stock_location_shop0
|
||||
-
|
||||
!record {model: stock.picking, id: incomming_chicago_warehouse2}:
|
||||
picking_type_id: chi_picking_type_in
|
||||
partner_id: base.res_partner_1
|
||||
location_id: stock.stock_location_suppliers
|
||||
location_dest_id: stock.stock_location_shop0
|
||||
move_lines:
|
||||
- product_id: product.product_delivery_01
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 45.0
|
||||
picking_type_id: chi_picking_type_in
|
||||
location_id: stock.stock_location_suppliers
|
||||
location_dest_id: stock.stock_location_shop0
|
||||
-
|
||||
!record {model: stock.picking, id: incomming_chicago_warehouse3}:
|
||||
picking_type_id: chi_picking_type_in
|
||||
origin: 'chicago_warehouse'
|
||||
partner_id: base.res_partner_1
|
||||
date: !eval "'%s-%s-2' % ((datetime.now()-timedelta(days=30)).month,(datetime.now()-timedelta(days=30)).month)"
|
||||
location_id: stock.stock_location_suppliers
|
||||
location_dest_id: stock.stock_location_shop0
|
||||
move_lines:
|
||||
- product_id: product.product_product_22
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 75.0
|
||||
picking_type_id: chi_picking_type_in
|
||||
location_id: stock.stock_location_suppliers
|
||||
location_dest_id: stock.stock_location_shop0
|
||||
-
|
||||
Create STOCK_PICKING_OUT for Chicago Warehouse
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_chicago_warehouse}:
|
||||
picking_type_id: chi_picking_type_out
|
||||
origin: 'outgoing_chicago_warehouse'
|
||||
partner_id: base.res_partner_1
|
||||
location_id: stock.stock_location_shop0
|
||||
location_dest_id: stock.stock_location_customers
|
||||
move_lines:
|
||||
- product_id: product.product_product_27
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 15.0
|
||||
picking_type_id: chi_picking_type_out
|
||||
location_id: stock.stock_location_shop0
|
||||
location_dest_id: stock.stock_location_customers
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_chicago_warehouse1}:
|
||||
picking_type_id: chi_picking_type_out
|
||||
origin: 'outgoing_shipment_chicago_warehouse'
|
||||
partner_id: base.res_partner_1
|
||||
location_id: stock.stock_location_shop0
|
||||
location_dest_id: stock.stock_location_customers
|
||||
move_lines:
|
||||
- product_id: product.product_product_6
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 180.0
|
||||
picking_type_id: chi_picking_type_out
|
||||
location_id: stock.stock_location_shop0
|
||||
location_dest_id: stock.stock_location_customers
|
||||
move_line_ids:
|
||||
- product_id: product.product_product_6
|
||||
qty_done: 100
|
||||
product_uom_id: product.product_uom_unit
|
||||
location_id: stock.stock_location_shop0
|
||||
location_dest_id: stock.stock_location_customers
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_chicago_warehouse2}:
|
||||
picking_type_id: chi_picking_type_out
|
||||
origin: 'chicago_warehouse'
|
||||
partner_id: base.res_partner_1
|
||||
location_id: stock.stock_location_shop0
|
||||
location_dest_id: stock.stock_location_customers
|
||||
move_lines:
|
||||
- product_id: product.product_delivery_02
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 45.0
|
||||
picking_type_id: chi_picking_type_out
|
||||
location_id: stock.stock_location_shop0
|
||||
location_dest_id: stock.stock_location_customers
|
||||
-
|
||||
!record {model: stock.picking, id: outgoing_chicago_warehouse3}:
|
||||
picking_type_id: chi_picking_type_out
|
||||
origin: 'outgoing chicago warehouse'
|
||||
location_id: stock.stock_location_shop0
|
||||
location_dest_id: stock.stock_location_customers
|
||||
partner_id: base.res_partner_1
|
||||
move_lines:
|
||||
- product_id: product.product_product_5
|
||||
product_uom: product.product_uom_unit
|
||||
product_uom_qty: 75.0
|
||||
picking_type_id: chi_picking_type_out
|
||||
location_id: stock.stock_location_shop0
|
||||
location_dest_id: stock.stock_location_customers
|
||||
-
|
||||
!python {model: stock.picking, id: False}: |
|
||||
self.browse([
|
||||
ref('outgoing_shipment_main_warehouse'),
|
||||
ref('outgoing_shipment_main_warehouse1'),
|
||||
ref('incomming_shipment2'),
|
||||
ref('incomming_shipment1'),
|
||||
ref('incomming_shipment3'),
|
||||
ref('outgoing_chicago_warehouse3'),
|
||||
ref('outgoing_chicago_warehouse2'),
|
||||
ref('outgoing_chicago_warehouse1'),
|
||||
ref('incomming_chicago_warehouse1'),
|
||||
ref('incomming_chicago_warehouse2'),
|
||||
ref('incomming_chicago_warehouse3')
|
||||
]).action_confirm()
|
||||
record = self.browse(ref('outgoing_shipment_main_warehouse1'))
|
||||
record_chi_in = self.browse(ref('incomming_chicago_warehouse1'))
|
||||
record_chi_out = self.browse(ref('outgoing_chicago_warehouse1'))
|
||||
if record.state != 'done' and record_chi_in.state != 'done' and record_chi_out.state != 'done':
|
||||
self.browse([
|
||||
ref('outgoing_shipment_main_warehouse'),
|
||||
ref('outgoing_chicago_warehouse3'),
|
||||
ref('outgoing_shipment_main_warehouse2')
|
||||
]).force_assign()
|
||||
self.browse([
|
||||
ref('outgoing_shipment_main_warehouse1'),
|
||||
ref('incomming_chicago_warehouse1'),
|
||||
ref('outgoing_chicago_warehouse1')
|
||||
]).do_transfer()
|
||||
@@ -0,0 +1,449 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<!-- create xml ids for demo data that are widely used in tests or in other codes, for more convenience -->
|
||||
<function model="ir.model.data" name="create">
|
||||
<value model="stock.warehouse" eval="{
|
||||
'name': 'chi_picking_type_in',
|
||||
'module':'stock',
|
||||
'model': 'stock.picking.type',
|
||||
'res_id': obj().env.ref('stock.stock_warehouse_shop0').in_type_id.id,
|
||||
}"/>
|
||||
</function>
|
||||
<function model="ir.model.data" name="create">
|
||||
<value model="stock.warehouse" eval="{
|
||||
'name': 'chi_picking_type_out',
|
||||
'module':'stock',
|
||||
'model': 'stock.picking.type',
|
||||
'res_id': obj().env.ref('stock.stock_warehouse_shop0').out_type_id.id,
|
||||
}"/>
|
||||
</function>
|
||||
<function model="ir.model.data" name="create">
|
||||
<value model="stock.warehouse" eval="{
|
||||
'name': 'stock_location_shop0',
|
||||
'module':'stock',
|
||||
'model': 'stock.picking.type',
|
||||
'res_id': obj().env.ref('stock.stock_warehouse_shop0').lot_stock_id.id,
|
||||
}"/>
|
||||
</function>
|
||||
<!-- avoid the xml id and the associated resource being dropped by the orm by manually making a hit on it -->
|
||||
<function model="ir.model.data" name="_update_dummy">
|
||||
<value>stock.picking.type</value>
|
||||
<value>stock</value>
|
||||
<value>chi_picking_type_in</value>
|
||||
</function>
|
||||
<function model="ir.model.data" name="_update_dummy">
|
||||
<value>stock.picking.type</value>
|
||||
<value>stock</value>
|
||||
<value>chi_picking_type_out</value>
|
||||
</function>
|
||||
<function model="ir.model.data" name="_update_dummy">
|
||||
<value>stock.picking.type</value>
|
||||
<value>stock</value>
|
||||
<value>stock_location_shop0</value>
|
||||
</function>
|
||||
|
||||
<record id="location_refrigerator_small" model="stock.location">
|
||||
<field name="name">Small Refrigerator</field>
|
||||
<field name="usage">internal</field>
|
||||
<field name="location_id" ref="stock.stock_location_14"/>
|
||||
</record>
|
||||
|
||||
<record id="product_icecream" model="product.product">
|
||||
<field name="default_code">001</field>
|
||||
<field name="name">Ice Cream</field>
|
||||
<field name="type">product</field>
|
||||
<field name="categ_id" ref="product.product_category_1"/>
|
||||
<field name="lst_price">100.0</field>
|
||||
<field name="standard_price">70.0</field>
|
||||
<field name="weight">1.0</field>
|
||||
<field name="uom_id" ref="product.product_uom_kgm"/>
|
||||
<field name="uom_po_id" ref="product.product_uom_kgm"/>
|
||||
<field name="property_stock_inventory" ref="stock.location_inventory"/>
|
||||
<field name="description">
|
||||
Ice cream can be mass-produced and thus is widely available in developed parts
|
||||
of the world. Ice cream can be purchased in large cartons (vats and squrounds)
|
||||
from supermarkets and grocery stores, in smaller quantities from ice cream shops,
|
||||
convenience stores, and milk bars, and in individual servings from small carts or
|
||||
vans at public events.
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="lot_icecream_0" model="stock.production.lot">
|
||||
<field name="name">0000000000017</field>
|
||||
<field name="product_id" ref="stock.product_icecream"/>
|
||||
</record>
|
||||
|
||||
<record id="lot_icecream_1" model="stock.production.lot">
|
||||
<field name="name">0000000000024</field>
|
||||
<field name="product_id" ref="stock.product_icecream"/>
|
||||
</record>
|
||||
|
||||
<record id="stock_inventory_icecream" model="stock.inventory">
|
||||
<field name="name">Inventory for icecream</field>
|
||||
<field name="filter">product</field>
|
||||
<field name="product_id" ref="stock.product_icecream"/>
|
||||
</record>
|
||||
|
||||
<record id="stock_inventory_line_icecream_lot0" model="stock.inventory.line">
|
||||
<field name="product_id" ref="stock.product_icecream"/>
|
||||
<field name="product_uom_id" ref="product.product_uom_kgm"/>
|
||||
<field name="inventory_id" ref="stock.stock_inventory_icecream"/>
|
||||
<field name="product_qty">50.0</field>
|
||||
<field name="prod_lot_id" ref="stock.lot_icecream_0"/>
|
||||
<field name="location_id" ref="stock.stock_location_14"/>
|
||||
</record>
|
||||
|
||||
<record id="stock_inventory_line_icecream_lot1" model="stock.inventory.line">
|
||||
<field name="product_id" ref="stock.product_icecream"/>
|
||||
<field name="product_uom_id" ref="product.product_uom_kgm"/>
|
||||
<field name="inventory_id" ref="stock.stock_inventory_icecream"/>
|
||||
<field name="product_qty">40.0</field>
|
||||
<field name="prod_lot_id" ref="stock.lot_icecream_1"/>
|
||||
<field name="location_id" ref="stock.stock_location_14"/>
|
||||
</record>
|
||||
|
||||
<!-- Create STOCK_MOVE for OUT -->
|
||||
<record id="outgoing_shipment_main_warehouse" model="stock.picking">
|
||||
<field name="picking_type_id" ref="stock.picking_type_out"/>
|
||||
<field name="origin">outgoing shipment main_warehouse</field>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="location_id" ref="stock.stock_location_stock"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_customers"/>
|
||||
<field name="move_lines" model="stock.move" eval="[(0, 0, {
|
||||
'name': obj().env.ref('product.product_product_27').name,
|
||||
'product_id': ref('product.product_product_27'),
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'product_uom_qty': 15.0,
|
||||
'picking_type_id': ref('stock.picking_type_out'),
|
||||
'location_id': ref('stock.stock_location_stock'),
|
||||
'location_dest_id': ref('stock.stock_location_customers'),
|
||||
})]"/>
|
||||
</record>
|
||||
|
||||
<record id="outgoing_shipment_main_warehouse1" model="stock.picking">
|
||||
<field name="picking_type_id" ref="stock.picking_type_out"/>
|
||||
<field name="origin">outgoing shipment</field>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="date" eval="'%s-%s-15' %((datetime.now()-timedelta(days=60)).year, (datetime.now()-timedelta(days=60)).month)"/>
|
||||
<field name="location_id" ref="stock.stock_location_stock"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_customers"/>
|
||||
<field name="move_lines" model="stock.move" eval="[(0, 0, {
|
||||
'name': obj().env.ref('product.product_product_6').name,
|
||||
'product_id': ref('product.product_product_6'),
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'product_uom_qty': 180.0,
|
||||
'picking_type_id': ref('stock.picking_type_out'),
|
||||
'location_id': ref('stock.stock_location_stock'),
|
||||
'location_dest_id': ref('stock.stock_location_customers'),
|
||||
'move_line_ids': [(0, 0, {
|
||||
'product_id': ref('product.product_product_6'),
|
||||
'qty_done': 100,
|
||||
'product_uom_id': ref('product.product_uom_unit'),
|
||||
'location_id': ref('stock.stock_location_stock'),
|
||||
'location_dest_id': ref('stock.stock_location_customers'),
|
||||
})],
|
||||
})]"/>
|
||||
</record>
|
||||
|
||||
<record id="outgoing_shipment_main_warehouse2" model="stock.picking">
|
||||
<field name="picking_type_id" ref="stock.picking_type_out"/>
|
||||
<field name="origin">outgoing shipment your_company warehouse</field>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="date" eval="'%s-%s-15' % ((datetime.now()-timedelta(days=30)).month,(datetime.now()-timedelta(days=30)).month)"/>
|
||||
<field name="location_id" ref="stock.stock_location_stock"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_customers"/>
|
||||
<field name="move_lines" model="stock.move" eval="[(0, 0, {
|
||||
'name': obj().env.ref('product.product_delivery_02').name,
|
||||
'product_id': ref('product.product_delivery_02'),
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'product_uom_qty': 45.0,
|
||||
'picking_type_id': ref('stock.picking_type_out'),
|
||||
'location_id': ref('stock.stock_location_stock'),
|
||||
'location_dest_id': ref('stock.stock_location_customers'),
|
||||
})]"/>
|
||||
</record>
|
||||
|
||||
<record id="outgoing_shipment_main_warehouse3" model="stock.picking">
|
||||
<field name="picking_type_id" ref="stock.picking_type_out"/>
|
||||
<field name="origin">your company warehouse</field>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="location_id" ref="stock.stock_location_stock"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_customers"/>
|
||||
<field name="move_lines" model="stock.move" eval="[(0, 0, {
|
||||
'name': obj().env.ref('product.product_product_5').name,
|
||||
'product_id': ref('product.product_product_5'),
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'product_uom_qty': 75.0,
|
||||
'picking_type_id': ref('stock.picking_type_out'),
|
||||
'location_id': ref('stock.stock_location_stock'),
|
||||
'location_dest_id': ref('stock.stock_location_customers'),
|
||||
})]"/>
|
||||
</record>
|
||||
|
||||
<!-- Create STOCK_PICKING for IN -->
|
||||
<record id="incomming_shipment" model="stock.picking">
|
||||
<field name="picking_type_id" ref="stock.picking_type_in"/>
|
||||
<field name="location_id" ref="stock.stock_location_suppliers"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_stock"/>
|
||||
<field name="move_lines" model="stock.move" eval="[(0, 0, {
|
||||
'name': obj().env.ref('stock.product_icecream').name,
|
||||
'product_id': ref('stock.product_icecream'),
|
||||
'product_uom': ref('product.product_uom_kgm'),
|
||||
'product_uom_qty': 50.0,
|
||||
'picking_type_id': ref('stock.picking_type_in'),
|
||||
'location_id': ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': ref('stock.stock_location_stock'),
|
||||
})]"/>
|
||||
</record>
|
||||
|
||||
<record id="incomming_shipment1" model="stock.picking">
|
||||
<field name="picking_type_id" ref="stock.picking_type_in"/>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="location_id" ref="stock.stock_location_suppliers"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_stock"/>
|
||||
<field name="move_lines" model="stock.move" eval="[(0, 0, {
|
||||
'name': obj().env.ref('product.product_delivery_01').name,
|
||||
'product_id': ref('product.product_delivery_01'),
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'product_uom_qty': 35.0,
|
||||
'picking_type_id': ref('stock.picking_type_in'),
|
||||
'location_id': ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': ref('stock.stock_location_stock'),
|
||||
})]"/>
|
||||
</record>
|
||||
|
||||
<record id="incomming_shipment2" model="stock.picking">
|
||||
<field name="picking_type_id" ref="stock.picking_type_in"/>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="location_id" ref="stock.stock_location_suppliers"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_stock"/>
|
||||
<field name="move_lines" model="stock.move" eval="[(0, 0, {
|
||||
'name': obj().env.ref('product.product_product_22').name,
|
||||
'product_id': ref('product.product_product_22'),
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'product_uom_qty': 125.0,
|
||||
'picking_type_id': ref('stock.picking_type_in'),
|
||||
'location_id': ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': ref('stock.stock_location_stock'),
|
||||
})]"/>
|
||||
</record>
|
||||
|
||||
<record id="incomming_shipment3" model="stock.picking">
|
||||
<field name="picking_type_id" ref="stock.picking_type_in"/>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="location_id" ref="stock.stock_location_suppliers"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_stock"/>
|
||||
<field name="move_lines" model="stock.move" eval="[(0, 0, {
|
||||
'name': obj().env.ref('product.product_product_10').name,
|
||||
'product_id': ref('product.product_product_10'),
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'product_uom_qty': 120.0,
|
||||
'picking_type_id': ref('stock.picking_type_in'),
|
||||
'location_id': ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': ref('stock.stock_location_stock'),
|
||||
})]"/>
|
||||
</record>
|
||||
|
||||
<record id="incomming_shipment4" model="stock.picking">
|
||||
<field name="picking_type_id" ref="stock.picking_type_in"/>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="location_id" ref="stock.stock_location_suppliers"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_stock"/>
|
||||
<field name="move_lines" model="stock.move" eval="[(0, 0, {
|
||||
'name': obj().env.ref('product.product_delivery_02').name,
|
||||
'product_id': ref('product.product_delivery_02'),
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'product_uom_qty': 50.0,
|
||||
'picking_type_id': ref('stock.picking_type_in'),
|
||||
'location_id': ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': ref('stock.stock_location_stock'),
|
||||
})]"/>
|
||||
</record>
|
||||
|
||||
<!-- Create STOCK_PICKING_IN for Chicago Warehouse -->
|
||||
<record id="incomming_chicago_warehouse" model="stock.picking">
|
||||
<field name="picking_type_id" ref="stock.chi_picking_type_in"/>
|
||||
<field name="origin">incoming_chicago_warehouse</field>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="location_id" ref="stock.stock_location_suppliers"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_shop0"/>
|
||||
<field name="move_lines" model="stock.move" eval="[(0, 0, {
|
||||
'name': obj().env.ref('product.product_product_9').name,
|
||||
'product_id': ref('product.product_product_9'),
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'product_uom_qty': 25.0,
|
||||
'picking_type_id': ref('stock.chi_picking_type_in'),
|
||||
'location_id': ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': ref('stock.stock_location_shop0'),
|
||||
})]"/>
|
||||
</record>
|
||||
|
||||
<record id="incomming_chicago_warehouse1" model="stock.picking">
|
||||
<field name="picking_type_id" ref="stock.chi_picking_type_in"/>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="location_id" ref="stock.stock_location_suppliers"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_shop0"/>
|
||||
<field name="move_lines" model="stock.move" eval="[(0, 0, {
|
||||
'name': obj().env.ref('product.product_delivery_01').name,
|
||||
'product_id': ref('product.product_delivery_01'),
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'product_uom_qty': 180.0,
|
||||
'picking_type_id': ref('stock.chi_picking_type_in'),
|
||||
'location_id': ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': ref('stock.stock_location_shop0'),
|
||||
'move_line_ids': [(0, 0, {
|
||||
'product_id': ref('product.product_delivery_01'),
|
||||
'qty_done': 100,
|
||||
'product_uom_id': ref('product.product_uom_unit'),
|
||||
'location_id': ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': ref('stock.stock_location_shop0'),
|
||||
})],
|
||||
})]"/>
|
||||
</record>
|
||||
|
||||
<record id="incomming_chicago_warehouse2" model="stock.picking">
|
||||
<field name="picking_type_id" ref="stock.chi_picking_type_in"/>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="location_id" ref="stock.stock_location_suppliers"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_shop0"/>
|
||||
<field name="move_lines" model="stock.move" eval="[(0, 0, {
|
||||
'name': obj().env.ref('product.product_delivery_01').name,
|
||||
'product_id': ref('product.product_delivery_01'),
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'product_uom_qty': 45.0,
|
||||
'picking_type_id': ref('stock.chi_picking_type_in'),
|
||||
'location_id': ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': ref('stock.stock_location_shop0'),
|
||||
})]"/>
|
||||
</record>
|
||||
|
||||
<record id="incomming_chicago_warehouse3" model="stock.picking">
|
||||
<field name="picking_type_id" ref="stock.chi_picking_type_in"/>
|
||||
<field name="origin">chicago_warehouse</field>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="date" eval="'%s-%s-2' % ((datetime.now()-timedelta(days=30)).month,(datetime.now()-timedelta(days=30)).month)"/>
|
||||
<field name="location_id" ref="stock.stock_location_suppliers"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_shop0"/>
|
||||
<field name="move_lines" model="stock.move" eval="[(0, 0, {
|
||||
'name': obj().env.ref('product.product_product_22').name,
|
||||
'product_id': ref('product.product_product_22'),
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'product_uom_qty': 75.0,
|
||||
'picking_type_id': ref('stock.chi_picking_type_in'),
|
||||
'location_id': ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': ref('stock.stock_location_shop0'),
|
||||
})]"/>
|
||||
</record>
|
||||
|
||||
<!-- Create STOCK_PICKING_OUT for Chicago Warehouse -->
|
||||
<record id="outgoing_chicago_warehouse" model="stock.picking">
|
||||
<field name="picking_type_id" ref="stock.chi_picking_type_out"/>
|
||||
<field name="origin">outgoing_chicago_warehouse</field>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="date" eval="'%s-%s-2' % ((datetime.now()-timedelta(days=30)).month,(datetime.now()-timedelta(days=30)).month)"/>
|
||||
<field name="location_id" ref="stock.stock_location_shop0"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_customers"/>
|
||||
<field name="move_lines" model="stock.move" eval="[(0, 0, {
|
||||
'name': obj().env.ref('product.product_product_27').name,
|
||||
'product_id': ref('product.product_product_27'),
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'product_uom_qty': 15.0,
|
||||
'picking_type_id': ref('stock.chi_picking_type_out'),
|
||||
'location_id': ref('stock.stock_location_shop0'),
|
||||
'location_dest_id': ref('stock.stock_location_customers'),
|
||||
})]"/>
|
||||
</record>
|
||||
|
||||
<record id="outgoing_chicago_warehouse1" model="stock.picking">
|
||||
<field name="picking_type_id" ref="stock.chi_picking_type_out"/>
|
||||
<field name="origin">outgoing_shipment_chicago_warehouse</field>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="date" eval="'%s-%s-2' % ((datetime.now()-timedelta(days=30)).month,(datetime.now()-timedelta(days=30)).month)"/>
|
||||
<field name="location_id" ref="stock.stock_location_shop0"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_customers"/>
|
||||
<field name="move_lines" model="stock.move" eval="[(0, 0, {
|
||||
'name': obj().env.ref('product.product_product_6').name,
|
||||
'product_id': ref('product.product_product_6'),
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'product_uom_qty': 180.0,
|
||||
'picking_type_id': ref('stock.chi_picking_type_out'),
|
||||
'location_id': ref('stock.stock_location_shop0'),
|
||||
'location_dest_id': ref('stock.stock_location_customers'),
|
||||
'move_line_ids': [(0, 0, {
|
||||
'product_id': ref('product.product_product_6'),
|
||||
'qty_done': 100,
|
||||
'product_uom_id': ref('product.product_uom_unit'),
|
||||
'location_id': ref('stock.stock_location_shop0'),
|
||||
'location_dest_id': ref('stock.stock_location_customers'),
|
||||
})],
|
||||
})]"/>
|
||||
</record>
|
||||
|
||||
<record id="outgoing_chicago_warehouse2" model="stock.picking">
|
||||
<field name="picking_type_id" ref="stock.chi_picking_type_out"/>
|
||||
<field name="origin">chicago_warehouse</field>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="date" eval="'%s-%s-2' % ((datetime.now()-timedelta(days=30)).month,(datetime.now()-timedelta(days=30)).month)"/>
|
||||
<field name="location_id" ref="stock.stock_location_shop0"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_customers"/>
|
||||
<field name="move_lines" model="stock.move" eval="[(0, 0, {
|
||||
'name': obj().env.ref('product.product_delivery_02').name,
|
||||
'product_id': ref('product.product_delivery_02'),
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'product_uom_qty': 45.0,
|
||||
'picking_type_id': ref('stock.chi_picking_type_out'),
|
||||
'location_id': ref('stock.stock_location_shop0'),
|
||||
'location_dest_id': ref('stock.stock_location_customers'),
|
||||
})]"/>
|
||||
</record>
|
||||
|
||||
<record id="outgoing_chicago_warehouse3" model="stock.picking">
|
||||
<field name="picking_type_id" ref="stock.chi_picking_type_out"/>
|
||||
<field name="origin">outgoing chicago warehouse</field>
|
||||
<field name="partner_id" ref="base.res_partner_1"/>
|
||||
<field name="location_id" ref="stock.stock_location_shop0"/>
|
||||
<field name="location_dest_id" ref="stock.stock_location_customers"/>
|
||||
<field name="move_lines" model="stock.move" eval="[(0, 0, {
|
||||
'name': obj().env.ref('product.product_product_5').name,
|
||||
'product_id': ref('product.product_product_5'),
|
||||
'product_uom': ref('product.product_uom_unit'),
|
||||
'product_uom_qty': 75.0,
|
||||
'picking_type_id': ref('stock.chi_picking_type_out'),
|
||||
'location_id': ref('stock.stock_location_shop0'),
|
||||
'location_dest_id': ref('stock.stock_location_customers'),
|
||||
})]"/>
|
||||
</record>
|
||||
|
||||
<function model="stock.picking" name="action_confirm">
|
||||
<value model="stock.picking" eval="[
|
||||
obj().env.ref('stock.outgoing_shipment_main_warehouse').id,
|
||||
obj().env.ref('stock.outgoing_shipment_main_warehouse1').id,
|
||||
obj().env.ref('stock.incomming_shipment2').id,
|
||||
obj().env.ref('stock.incomming_shipment1').id,
|
||||
obj().env.ref('stock.incomming_shipment3').id,
|
||||
obj().env.ref('stock.outgoing_chicago_warehouse3').id,
|
||||
obj().env.ref('stock.outgoing_chicago_warehouse2').id,
|
||||
obj().env.ref('stock.outgoing_chicago_warehouse1').id,
|
||||
obj().env.ref('stock.incomming_chicago_warehouse1').id,
|
||||
obj().env.ref('stock.incomming_chicago_warehouse2').id,
|
||||
obj().env.ref('stock.incomming_chicago_warehouse3').id]"/>
|
||||
</function>
|
||||
|
||||
<function model="stock.picking" name="force_assign">
|
||||
<value model="stock.picking" eval="[
|
||||
obj().env.ref('stock.outgoing_shipment_main_warehouse').id,
|
||||
obj().env.ref('stock.outgoing_chicago_warehouse3').id,
|
||||
obj().env.ref('stock.outgoing_shipment_main_warehouse2').id]"/>
|
||||
</function>
|
||||
|
||||
<function model="stock.picking" name="do_transfer">
|
||||
<value model="stock.picking" eval="[
|
||||
obj().env.ref('stock.outgoing_shipment_main_warehouse1').id,
|
||||
obj().env.ref('stock.incomming_chicago_warehouse1').id,
|
||||
obj().env.ref('stock.outgoing_chicago_warehouse1').id]"/>
|
||||
</function>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="stock_location_14" model="stock.location">
|
||||
<field name="name">Shelf 2</field>
|
||||
<field name="posx">0</field>
|
||||
<field name="location_id" model="stock.location"
|
||||
eval="obj().env.ref('stock.warehouse0').lot_stock_id.id"/>
|
||||
</record>
|
||||
<record id="stock_location_components" model="stock.location">
|
||||
<field name="name">Shelf 1</field>
|
||||
<field name="posx">0</field>
|
||||
<field name="location_id" model="stock.location"
|
||||
eval="obj().env.ref('stock.warehouse0').lot_stock_id.id"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,17 +0,0 @@
|
||||
-
|
||||
!record {model: stock.location, id: stock_location_14}:
|
||||
name: Shelf 2
|
||||
posx: 0
|
||||
|
||||
-
|
||||
!record {model: stock.location, id: stock_location_components}:
|
||||
name: Shelf 1
|
||||
posx: 0
|
||||
|
||||
-
|
||||
!python {model: ir.model.data, id: False}: |
|
||||
main_warehouse = self.env['stock.warehouse'].browse(ref('warehouse0'))
|
||||
self.env['stock.location'].browse([
|
||||
ref('stock_location_14'),
|
||||
ref('stock_location_components')
|
||||
]).write({'location_id':main_warehouse.lot_stock_id.id})
|
||||
@@ -0,0 +1,27 @@
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="location_order" model="stock.location">
|
||||
<field name="name">Order Processing</field>
|
||||
<field name="usage">internal</field>
|
||||
<field name="location_id" ref="stock.stock_location_company"/>
|
||||
</record>
|
||||
|
||||
<record id="location_dispatch_zone" model="stock.location">
|
||||
<field name="name">Dispatch Zone</field>
|
||||
<field name="usage">internal</field>
|
||||
<field name="location_id" ref="stock.location_order"/>
|
||||
</record>
|
||||
|
||||
<record id="location_gate_a" model="stock.location">
|
||||
<field name="name">Gate A</field>
|
||||
<field name="usage">internal</field>
|
||||
<field name="location_id" ref="stock.location_dispatch_zone"/>
|
||||
</record>
|
||||
|
||||
<record id="location_gate_b" model="stock.location">
|
||||
<field name="name">Gate B</field>
|
||||
<field name="usage">internal</field>
|
||||
<field name="location_id" ref="stock.location_dispatch_zone"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,20 +0,0 @@
|
||||
-
|
||||
!record {model: stock.location, id: location_order}:
|
||||
name: Order Processing
|
||||
usage: internal
|
||||
location_id: stock.stock_location_company
|
||||
-
|
||||
!record {model: stock.location, id: location_dispatch_zone}:
|
||||
name: Dispatch Zone
|
||||
usage: internal
|
||||
location_id: location_order
|
||||
-
|
||||
!record {model: stock.location, id: location_gate_a}:
|
||||
name: Gate A
|
||||
usage: internal
|
||||
location_id: location_dispatch_zone
|
||||
-
|
||||
!record {model: stock.location, id: location_gate_b}:
|
||||
name: Gate B
|
||||
usage: internal
|
||||
location_id: location_dispatch_zone
|
||||
@@ -9,7 +9,8 @@
|
||||
<field name="product_uom" ref="product.product_uom_unit"/>
|
||||
<field model="stock.warehouse" name="warehouse_id" search="[]"/>
|
||||
<field name="product_id" ref="product.product_delivery_02"/>
|
||||
<!-- <field name="location_id" ref="stock.stock_location_stock"/> -->
|
||||
<field name="location_id" model="stock.location"
|
||||
eval="obj().env.ref('stock.warehouse0').lot_stock_id.id"/>
|
||||
</record>
|
||||
<record id="stock_warehouse_orderpoint_2" model="stock.warehouse.orderpoint">
|
||||
<field name="product_max_qty">12.0</field>
|
||||
@@ -17,7 +18,8 @@
|
||||
<field name="product_uom" ref="product.product_uom_unit"/>
|
||||
<field model="stock.warehouse" name="warehouse_id" search="[]"/>
|
||||
<field name="product_id" ref="product.product_product_20"/>
|
||||
<!-- <field name="location_id" ref="stock.stock_location_stock"/> -->
|
||||
<field name="location_id" model="stock.location"
|
||||
eval="obj().env.ref('stock.warehouse0').lot_stock_id.id"/>
|
||||
</record>
|
||||
<record id="stock_warehouse_orderpoint_5" model="stock.warehouse.orderpoint">
|
||||
<field name="product_max_qty">5.0</field>
|
||||
@@ -25,7 +27,9 @@
|
||||
<field name="product_uom" ref="product.product_uom_unit"/>
|
||||
<field model="stock.warehouse" name="warehouse_id" search="[]"/>
|
||||
<field name="product_id" ref="product.product_delivery_01"/>
|
||||
<!-- <field name="location_id" ref="stock.stock_location_stock"/> -->
|
||||
<field name="location_id" model="stock.location"
|
||||
eval="obj().env.ref('stock.warehouse0').lot_stock_id.id"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,6 +0,0 @@
|
||||
-
|
||||
!python {model: stock.location, id: False}: |
|
||||
main_warehouse = self.env['stock.warehouse'].browse(ref('warehouse0'))
|
||||
main_stock = main_warehouse.lot_stock_id.id
|
||||
stowar_ops = self.env['stock.warehouse.orderpoint'].search([])
|
||||
stowar_ops.write({'location_id':main_stock})
|
||||
@@ -184,7 +184,10 @@ class ProcurementGroup(models.Model):
|
||||
if not rule:
|
||||
raise UserError(_('No procurement rule found. Please verify the configuration of your routes'))
|
||||
|
||||
getattr(rule, '_run_%s' % rule.action)(product_id, product_qty, product_uom, location_id, name, origin, values)
|
||||
if hasattr(rule, '_run_%s' % rule.action):
|
||||
getattr(rule, '_run_%s' % rule.action)(product_id, product_qty, product_uom, location_id, name, origin, values)
|
||||
else:
|
||||
_logger.error("The method _run_%s doesn't exist on the procument rules" % rule.action)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -12,7 +12,6 @@ class Company(models.Model):
|
||||
'stock.location', 'Internal Transit Location', on_delete="restrict",
|
||||
help="Technical field used for resupply routes between warehouses that belong to this company")
|
||||
|
||||
@api.one
|
||||
def create_transit_location(self):
|
||||
'''Create a transit location with company_id being the given company_id. This is needed
|
||||
in case of resuply routes between warehouses belonging to the same company, because
|
||||
@@ -20,13 +19,14 @@ class Company(models.Model):
|
||||
'''
|
||||
# TDE FIXME: called in data - should be done in init ??
|
||||
parent_location = self.env.ref('stock.stock_location_locations', raise_if_not_found=False)
|
||||
location = self.env['stock.location'].create({
|
||||
'name': _('%s: Transit Location') % self.name,
|
||||
'usage': 'transit',
|
||||
'location_id': parent_location and parent_location.id or False,
|
||||
})
|
||||
location.sudo().write({'company_id': self.id})
|
||||
self.write({'internal_transit_location_id': location.id})
|
||||
for company in self:
|
||||
location = self.env['stock.location'].create({
|
||||
'name': _('%s: Transit Location') % company.name,
|
||||
'usage': 'transit',
|
||||
'location_id': parent_location and parent_location.id or False,
|
||||
})
|
||||
location.sudo().write({'company_id': company.id})
|
||||
company.write({'internal_transit_location_id': location.id})
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
|
||||
@@ -480,7 +480,7 @@ class Picking(models.Model):
|
||||
# As it is a create the format will be a list of (0, 0, dict)
|
||||
if vals.get('move_lines') and vals.get('location_id') and vals.get('location_dest_id'):
|
||||
for move in vals['move_lines']:
|
||||
if len(move) == 3:
|
||||
if len(move) == 3 and move[0] == 0:
|
||||
move[2]['location_id'] = vals['location_id']
|
||||
move[2]['location_dest_id'] = vals['location_dest_id']
|
||||
res = super(Picking, self).create(vals)
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
-
|
||||
Create a new stockable product
|
||||
-
|
||||
!record {model: product.product, id: product1, view: False}:
|
||||
name: Nice product
|
||||
type: product
|
||||
categ_id: product.product_category_1
|
||||
list_price: 100.0
|
||||
standard_price: 70.0
|
||||
seller_ids:
|
||||
- delay: 1
|
||||
name: base.res_partner_2
|
||||
min_qty: 2.0
|
||||
uom_id: product.product_uom_unit
|
||||
uom_po_id: product.product_uom_unit
|
||||
-
|
||||
Create an incoming picking for this product of 300 PCE from suppliers to stock
|
||||
-
|
||||
!record {model: stock.picking, id: pick1}:
|
||||
name: Incoming picking
|
||||
partner_id: base.res_partner_2
|
||||
picking_type_id: picking_type_in
|
||||
move_lines:
|
||||
- product_id: product1
|
||||
product_uom_qty: 300.00
|
||||
location_id: stock_location_suppliers
|
||||
location_dest_id: stock_location_stock
|
||||
-
|
||||
Confirm and assign picking
|
||||
-
|
||||
!python {model: stock.picking, id: pick1}: |
|
||||
self.action_confirm()
|
||||
self.action_assign()
|
||||
-
|
||||
Put 120 pieces on Pallet 1 (package), 120 pieces on Pallet 2 with lot A and 60 pieces on Pallet 3
|
||||
-
|
||||
!python {model: stock.picking, id: pick1}: |
|
||||
#Change quantity of first to 120 and create 2 others quant operations
|
||||
stock_pack = self.env['stock.move.line']
|
||||
stock_quant_pack = self.env['stock.quant.package']
|
||||
#create lot A
|
||||
lot_a = self.env['stock.production.lot'].create({'name': 'Lot A', 'product_id': ref('product1')})
|
||||
#create package
|
||||
package1 = stock_quant_pack.create({'name': 'Pallet 1'})
|
||||
package2 = stock_quant_pack.create({'name': 'Pallet 2'})
|
||||
package3 = stock_quant_pack.create({'name': 'Pallet 3'})
|
||||
#Create package for each line and assign it as result_package_id
|
||||
#create pack operation
|
||||
self.move_line_ids[0].write({'result_package_id': package1.id, 'qty_done': 120})
|
||||
new_pack1 = stock_pack.create({
|
||||
'product_id': ref('product1'),
|
||||
'product_uom_id': ref('product.product_uom_unit'),
|
||||
'picking_id': ref('pick1'),
|
||||
'lot_id': lot_a.id,
|
||||
'qty_done': 120,
|
||||
'result_package_id': package2.id,
|
||||
'location_id': ref('stock_location_suppliers'),
|
||||
'location_dest_id': ref('stock_location_stock')
|
||||
})
|
||||
new_pack2 = stock_pack.create({
|
||||
'product_id': ref('product1'),
|
||||
'product_uom_id': ref('product.product_uom_unit'),
|
||||
'picking_id': ref('pick1'),
|
||||
'result_package_id': package3.id,
|
||||
'qty_done': 60,
|
||||
'location_id': ref('stock_location_suppliers'),
|
||||
'location_dest_id': ref('stock_location_stock')
|
||||
})
|
||||
-
|
||||
Transfer the receipt
|
||||
-
|
||||
!python {model: stock.picking, id: pick1}: |
|
||||
self.action_done()
|
||||
-
|
||||
Check the system created 5 quants one with 120 pieces on pallet 1, one with 120 pieces on pallet 2 with lot A and 60 pieces on pallet 3 in stock
|
||||
and 1 with -180 pieces and another with -120 piece lot A in supplier
|
||||
-
|
||||
!python {model: stock.quant, id: False}: |
|
||||
quants = self.search([('product_id','=',ref('product1'))])
|
||||
assert len(quants.ids) == 5, "The number of quants created is not correct"
|
||||
for quant in quants:
|
||||
if quant.package_id.name == 'Pallet 1':
|
||||
assert quant.quantity == 120, "Should have 120 pieces on pallet 1"
|
||||
elif quant.package_id.name == 'Pallet 2':
|
||||
assert quant.quantity == 120, "Should have 120 pieces on pallet 2"
|
||||
elif quant.package_id.name == 'Pallet 3':
|
||||
assert quant.quantity == 60, "Should have 60 pieces on pallet 3"
|
||||
-
|
||||
Check there is no backorder or extra moves created
|
||||
-
|
||||
!python {model: stock.picking, id: pick1}: |
|
||||
backorder = self.search([('backorder_id', '=', ref('pick1'))])
|
||||
assert not backorder, ""
|
||||
#Check extra moves created
|
||||
assert len(self.move_lines) == 1, ""
|
||||
-
|
||||
Make a delivery order of 300 pieces to the customer
|
||||
-
|
||||
!record {model: stock.picking, id: delivery_order1}:
|
||||
name: outgoing picking
|
||||
partner_id: base.res_partner_4
|
||||
picking_type_id: stock.picking_type_out
|
||||
move_lines:
|
||||
- product_id: product1
|
||||
product_uom_qty: 300.00
|
||||
location_id: stock_location_stock
|
||||
location_dest_id: stock_location_customers
|
||||
-
|
||||
Assign and confirm
|
||||
-
|
||||
!python {model: stock.picking, id: delivery_order1}: |
|
||||
self.action_confirm()
|
||||
self.action_assign()
|
||||
-
|
||||
Instead of doing the 300 pieces, you decide to take pallet 1 (do not mention product in operation here) and 20 pieces from lot A and 10 pieces from pallet 3
|
||||
-
|
||||
!python {model: stock.picking, id: delivery_order1}: |
|
||||
for rec in self.move_line_ids:
|
||||
if rec.package_id.name == 'Pallet 1':
|
||||
rec.qty_done = 120
|
||||
rec.result_package_id = False
|
||||
if rec.package_id.name == 'Pallet 2':
|
||||
rec.qty_done = 20
|
||||
rec.result_package_id = False
|
||||
if rec.package_id.name == 'Pallet 3':
|
||||
rec.qty_done = 10
|
||||
rec.result_package_id = False
|
||||
-
|
||||
Process this picking
|
||||
-
|
||||
!python {model: stock.picking, id: delivery_order1 }: |
|
||||
self.action_done()
|
||||
-
|
||||
Check the quants that you have 100 pieces of pallet 2 and 50 pieces of pallet 3 in stock
|
||||
-
|
||||
!python {model: stock.quant, id: False}: |
|
||||
records = self.search([('product_id','=',ref('product1'))])
|
||||
for rec in records:
|
||||
if rec.package_id.name == 'Pallet 2' and rec.location_id.id == ref('stock_location_stock'):
|
||||
assert rec.quantity == 100, "Should have 100 pieces in stock on pallet 2, got " + str(rec.quantity)
|
||||
elif rec.package_id.name == 'Pallet 3' and rec.location_id.id == ref('stock_location_stock'):
|
||||
assert rec.quantity == 50, "Should have 50 pieces in stock on pallet 3"
|
||||
else:
|
||||
assert rec.location_id.id != ref('stock_location_stock'), "Unrecognized quant in stock"
|
||||
-
|
||||
Check a backorder was created and on that backorder, prepare partial and process backorder
|
||||
-
|
||||
!python {model: stock.picking, id: False}: |
|
||||
backorders = self.search([('backorder_id', '=', ref('delivery_order1'))])
|
||||
assert backorders, "Backorder should have been created"
|
||||
backorders.action_assign()
|
||||
picking = backorders[0]
|
||||
assert len(picking.move_line_ids) == 2, "Wrong number of pack operation"
|
||||
for pack_op in picking.move_line_ids:
|
||||
assert pack_op.package_id.name in ('Pallet 2', 'Pallet 3'), "Wrong pallet info in pack operation (%s found)" % (pack_op.package_id.name)
|
||||
if pack_op.package_id.name == 'Pallet 2':
|
||||
assert pack_op.product_qty == 100
|
||||
pack_op.qty_done = 100
|
||||
elif pack_op.package_id.name == 'Pallet 3':
|
||||
assert pack_op.product_qty == 50
|
||||
pack_op.qty_done = 50
|
||||
backorders.action_done()
|
||||
-
|
||||
Check there are still 0 pieces in stock
|
||||
-
|
||||
!python {model: stock.quant, id: False}: |
|
||||
records = self.search([('product_id','=',ref('product1')), ('location_id', '=', ref('stock_location_stock'))])
|
||||
total_qty = 0
|
||||
for rec in records:
|
||||
total_qty += rec.quantity
|
||||
product = self.env["product.product"].browse(ref('product1'))
|
||||
assert total_qty == 0, "Total quantity in stock should be 0 as the backorder took everything out of stock"
|
||||
assert product.qty_available == 0, "Quantity available should be 0 too"
|
||||
@@ -1,163 +0,0 @@
|
||||
-
|
||||
Create a new "negative" stockable product
|
||||
-
|
||||
!record {model: product.product, id: product_neg, view: False}:
|
||||
name: Negative product
|
||||
type: product
|
||||
categ_id: product.product_category_1
|
||||
list_price: 100.0
|
||||
standard_price: 70.0
|
||||
seller_ids:
|
||||
- delay: 1
|
||||
name: base.res_partner_2
|
||||
min_qty: 2.0
|
||||
uom_id: product.product_uom_unit
|
||||
uom_po_id: product.product_uom_unit
|
||||
-
|
||||
Create an incoming picking for this product of 300 PCE from suppliers to stock
|
||||
-
|
||||
!record {model: stock.picking, id: pick_neg}:
|
||||
name: Incoming picking (negative product)
|
||||
partner_id: base.res_partner_2
|
||||
picking_type_id: picking_type_in
|
||||
move_lines:
|
||||
- product_id: product_neg
|
||||
product_uom_qty: 300.00
|
||||
location_id: stock_location_suppliers
|
||||
location_dest_id: stock_location_stock
|
||||
-
|
||||
Confirm and assign picking
|
||||
-
|
||||
!python {model: stock.picking, id: pick_neg}: |
|
||||
self.action_confirm()
|
||||
self.action_assign()
|
||||
-
|
||||
Put 120 pieces on Palneg 1 (package), 120 pieces on Palneg 2 with lot A and 60 pieces on Palneg 3
|
||||
-
|
||||
!python {model: stock.picking, id: pick_neg}: |
|
||||
#Change quantity of first to 120 and create 2 others quant operations
|
||||
stock_pack = self.env['stock.move.line']
|
||||
stock_quant_pack = self.env['stock.quant.package']
|
||||
#create lot A
|
||||
lot_a = self.env['stock.production.lot'].create({'name': 'Lot neg', 'product_id': ref('product_neg')})
|
||||
#create package
|
||||
package1 = stock_quant_pack.create({'name': 'Palneg 1'})
|
||||
package2 = stock_quant_pack.create({'name': 'Palneg 2'})
|
||||
package3 = stock_quant_pack.create({'name': 'Palneg 3'})
|
||||
#Create package for each line and assign it as result_package_id
|
||||
#create pack operation
|
||||
self.move_line_ids[0].write({'result_package_id': package1.id, 'qty_done': 120})
|
||||
new_pack1 = stock_pack.create({
|
||||
'product_id': ref('product_neg'),
|
||||
'product_uom_id': ref('product.product_uom_unit'),
|
||||
'picking_id': ref('pick_neg'),
|
||||
'lot_id': lot_a.id,
|
||||
'qty_done': 120,
|
||||
'result_package_id': package2.id,
|
||||
'location_id': ref('stock_location_suppliers'),
|
||||
'location_dest_id': ref('stock_location_stock')
|
||||
})
|
||||
new_pack2 = stock_pack.create({
|
||||
'product_id': ref('product_neg'),
|
||||
'product_uom_id': ref('product.product_uom_unit'),
|
||||
'picking_id': ref('pick_neg'),
|
||||
'result_package_id': package3.id,
|
||||
'qty_done': 60,
|
||||
'location_id': ref('stock_location_suppliers'),
|
||||
'location_dest_id': ref('stock_location_stock')
|
||||
})
|
||||
-
|
||||
Transfer the receipt
|
||||
-
|
||||
!python {model: stock.picking, id: pick_neg}: |
|
||||
self.do_transfer()
|
||||
-
|
||||
Make a delivery order of 300 pieces to the customer
|
||||
-
|
||||
!record {model: stock.picking, id: delivery_order_neg}:
|
||||
name: outgoing picking (negative product)
|
||||
partner_id: base.res_partner_4
|
||||
picking_type_id: stock.picking_type_out
|
||||
move_lines:
|
||||
- product_id: product_neg
|
||||
product_uom_qty: 300.00
|
||||
location_id: stock_location_stock
|
||||
location_dest_id: stock_location_customers
|
||||
-
|
||||
Assign and confirm
|
||||
-
|
||||
!python {model: stock.picking, id: delivery_order_neg}: |
|
||||
self.action_confirm()
|
||||
self.action_assign()
|
||||
-
|
||||
Instead of doing the 300 pieces, you decide to take pallet 1 (do not mention product in operation here) and 140 pieces from lot A/pallet 2 and 10 pieces from pallet 3
|
||||
-
|
||||
!python {model: stock.picking, id: delivery_order_neg}: |
|
||||
for rec in self.move_line_ids:
|
||||
if rec.package_id.name == 'Palneg 1':
|
||||
rec.qty_done = rec.product_qty
|
||||
rec.result_package_id = False
|
||||
elif rec.package_id.name == 'Palneg 2' and rec.lot_id.name == 'Lot neg' :
|
||||
rec.write({
|
||||
'qty_done': 140,
|
||||
'result_package_id': False,
|
||||
})
|
||||
elif rec.package_id.name == 'Palneg 3':
|
||||
rec.qty_done = 10
|
||||
rec.result_package_id = False
|
||||
-
|
||||
Process this picking
|
||||
-
|
||||
!python {model: stock.picking, id: delivery_order_neg}: |
|
||||
self.do_transfer()
|
||||
-
|
||||
Check the quants that you have -20 pieces pallet 2 in stock, and a total quantity of 50 in stock from pallet 3 (should be 20+30, as it has been split by reservation)
|
||||
-
|
||||
!python {model: stock.quant, id: False}: |
|
||||
records = self.search([('product_id','=',ref('product_neg'))])
|
||||
pallet_3_stock_qty = 0
|
||||
for rec in records:
|
||||
if rec.package_id.name == 'Palneg 2' and rec.location_id.id == ref('stock_location_stock'):
|
||||
assert rec.quantity == -20, "Should have -20 pieces in stock on pallet 2. Got " + str(rec.quantity)
|
||||
assert rec.lot_id.name == 'Lot neg', "It should have kept its Lot"
|
||||
elif rec.package_id.name == 'Palneg 3' and rec.location_id.id == ref('stock_location_stock'):
|
||||
pallet_3_stock_qty += rec.quantity
|
||||
else:
|
||||
assert rec.location_id.id != ref('stock_location_stock'), "Unrecognized quant in stock"
|
||||
assert pallet_3_stock_qty == 50, "Should have 50 pieces in stock on pallet 3"
|
||||
-
|
||||
Create a picking for reconciling the negative quant
|
||||
-
|
||||
!record {model: stock.picking, id: delivery_reconcile}:
|
||||
name: reconciling_delivery
|
||||
partner_id: base.res_partner_4
|
||||
picking_type_id: stock.picking_type_in
|
||||
move_lines:
|
||||
- product_id: product_neg
|
||||
product_uom_qty: 20.0
|
||||
location_id: stock_location_suppliers
|
||||
location_dest_id: stock_location_stock
|
||||
-
|
||||
Receive 20 products with lot neg in stock with a new incoming shipment that should be on pallet 2
|
||||
-
|
||||
!python {model: stock.picking, id: delivery_reconcile}: |
|
||||
self.action_confirm()
|
||||
pack_obj = self.env["stock.quant.package"]
|
||||
lot = self.env["stock.production.lot"].search([('product_id', '=', ref('product_neg')), ('name','=','Lot neg')], limit=1)
|
||||
pack = pack_obj.search([('name', '=', 'Palneg 2')], limit=1)
|
||||
self.move_line_ids[0].write({'lot_id': lot.id, 'qty_done': 20.0, 'result_package_id': pack.id})
|
||||
self.do_transfer()
|
||||
-
|
||||
Check the negative quant was reconciled
|
||||
-
|
||||
!python {model: stock.quant, id: False}: |
|
||||
neg_quants = self.search([('product_id','=', ref('product_neg')), ('quantity', '<', 0), ('location_id.id', '!=',
|
||||
ref('stock_location_suppliers'))])
|
||||
assert len(neg_quants) == 0, "Negative quants should have been reconciled"
|
||||
pick = self.env['stock.picking'].browse(ref('delivery_reconcile'))
|
||||
customer_quant = self.search([
|
||||
('product_id', '=', ref('product_neg')),
|
||||
('location_id', '=', ref('stock_location_customers')),
|
||||
('lot_id.name','=', 'Lot neg'),
|
||||
('quantity','=', 20)
|
||||
])
|
||||
@@ -1,54 +0,0 @@
|
||||
-
|
||||
Create a product route containing a procurement rule that will
|
||||
generate a move from Stock for every procurement created in Output
|
||||
-
|
||||
!record {model: stock.location.route, id: product_route}:
|
||||
name: Stock -> output route
|
||||
product_selectable: True
|
||||
pull_ids:
|
||||
- name: Stock -> output rule
|
||||
action: move
|
||||
picking_type_id: stock.picking_type_internal
|
||||
location_src_id: stock.stock_location_stock
|
||||
location_id: stock.stock_location_output
|
||||
-
|
||||
Set this route on `product.product_product_3`
|
||||
-
|
||||
!python {model: product.product, id: product.product_product_3}: |
|
||||
self.write({'route_ids': [(4, ref('stock.product_route'))]})
|
||||
-
|
||||
Create Delivery Order of 10 `product.product_product_3`
|
||||
from Output -> Customer
|
||||
-
|
||||
!record {model: stock.picking, id: pick_output}:
|
||||
name: Delivery order for procurement
|
||||
partner_id: base.res_partner_2
|
||||
picking_type_id: stock.picking_type_out
|
||||
location_id: stock.stock_location_output
|
||||
location_dest_id: stock.stock_location_customers
|
||||
move_lines:
|
||||
- product_id: product.product_product_3
|
||||
product_uom_qty: 10.00
|
||||
procure_method: make_to_order
|
||||
-
|
||||
Confirm delivery order.
|
||||
-
|
||||
!python {model: stock.picking, id: pick_output}: |
|
||||
self.action_confirm()
|
||||
-
|
||||
I run the scheduler.
|
||||
-
|
||||
!python {model: procurement.group, id: False}: |
|
||||
self.run_scheduler()
|
||||
-
|
||||
Check that a picking was created from stock to output.
|
||||
-
|
||||
!python {model: stock.move, id: False}: |
|
||||
picking = self.env["stock.picking"].browse(ref("pick_output"))
|
||||
moves = self.search([
|
||||
('product_id', '=', ref('product.product_product_3')),
|
||||
('location_id', '=', ref('stock.stock_location_stock')),
|
||||
('location_dest_id', '=', ref('stock.stock_location_output')),
|
||||
('move_dest_ids', 'in', [picking.move_lines[0].id])
|
||||
])
|
||||
assert len(moves.ids) == 1, "It should have created a picking from Stock to Output with the original picking as destination"
|
||||
@@ -1,28 +0,0 @@
|
||||
-
|
||||
Create a user as 'Stock Manager'
|
||||
-
|
||||
!record {model: res.users, id: res_users_stock_manager, view: False}:
|
||||
company_id: base.main_company
|
||||
name: Stock Manager
|
||||
login: sam
|
||||
email: stockmanager@yourcompany.com
|
||||
-
|
||||
I added groups for Stock Manager.
|
||||
-
|
||||
!record {model: res.users, id: res_users_stock_manager}:
|
||||
groups_id:
|
||||
- stock.group_stock_manager
|
||||
-
|
||||
Create a user as 'Stock User'
|
||||
-
|
||||
!record {model: res.users, id: res_users_stock_user, view: False}:
|
||||
company_id: base.main_company
|
||||
name: Stock User
|
||||
login: sau
|
||||
email: stockuser@yourcompany.com
|
||||
-
|
||||
I added groups for Stock User.
|
||||
-
|
||||
!record {model: res.users, id: res_users_stock_user}:
|
||||
groups_id:
|
||||
- stock.group_stock_user
|
||||
@@ -1,159 +0,0 @@
|
||||
-
|
||||
Create a new stockable product
|
||||
-
|
||||
!record {model: product.product, id: product_wise}:
|
||||
name: Wise Unit
|
||||
type: product
|
||||
categ_id: product.product_category_1
|
||||
uom_id: product.product_uom_unit
|
||||
uom_po_id: product.product_uom_unit
|
||||
-
|
||||
Create an incoming picking for this product of 10 PCE from suppliers to stock
|
||||
-
|
||||
!record {model: stock.picking, id: pick1_wise}:
|
||||
name: Incoming picking (wise unit)
|
||||
partner_id: base.res_partner_2
|
||||
picking_type_id: picking_type_in
|
||||
move_lines:
|
||||
- product_id: product_wise
|
||||
product_uom_qty: 10.00
|
||||
location_id: stock_location_suppliers
|
||||
location_dest_id: stock_location_stock
|
||||
-
|
||||
Confirm and assign picking and prepare partial
|
||||
-
|
||||
!python {model: stock.picking, id: pick1_wise}: |
|
||||
self.action_confirm()
|
||||
self.action_assign()
|
||||
-
|
||||
Put 4 pieces in shelf1 and 6 pieces in shelf2
|
||||
-
|
||||
!python {model: stock.picking, id: pick1_wise}: |
|
||||
stock_pack = self.env['stock.move.line']
|
||||
stock_quant_pack = self.env['stock.quant.package']
|
||||
package1 = stock_quant_pack.create({'name': 'Pack 1'})
|
||||
self.move_line_ids[0].write({
|
||||
'result_package_id': package1.id,
|
||||
'qty_done': 4,
|
||||
'location_dest_id': ref('stock_location_components')
|
||||
})
|
||||
new_pack1 = stock_pack.create({
|
||||
'product_id': ref('product_wise'),
|
||||
'product_uom_id': ref('product.product_uom_unit'),
|
||||
'picking_id': ref('pick1_wise'),
|
||||
'qty_done': 6.0,
|
||||
'location_id': ref('stock_location_suppliers'),
|
||||
'location_dest_id': ref('stock_location_14')
|
||||
})
|
||||
-
|
||||
Transfer the receipt
|
||||
-
|
||||
!python {model: stock.picking, id: pick1_wise}: |
|
||||
self.do_transfer()
|
||||
-
|
||||
Check the system created 3 quants
|
||||
-
|
||||
!python {model: stock.quant, id: False}: |
|
||||
records = self.search([('product_id','=',ref('product_wise'))])
|
||||
assert len(records.ids) == 3, "The number of quants created is not correct"
|
||||
-
|
||||
Make a delivery order of 5 pieces to the customer
|
||||
-
|
||||
!record {model: stock.picking, id: delivery_order_wise1}:
|
||||
name: outgoing picking 1 (wise unit)
|
||||
partner_id: base.res_partner_4
|
||||
picking_type_id: stock.picking_type_out
|
||||
move_lines:
|
||||
- product_id: product_wise
|
||||
product_uom_qty: 5.0
|
||||
location_id: stock_location_stock
|
||||
location_dest_id: stock_location_customers
|
||||
-
|
||||
Assign and confirm
|
||||
-
|
||||
!python {model: stock.picking, id: delivery_order_wise1}: |
|
||||
self.action_confirm()
|
||||
self.action_assign()
|
||||
assert self.state == 'assigned'
|
||||
-
|
||||
Make a delivery order of 5 pieces to the customer
|
||||
-
|
||||
!record {model: stock.picking, id: delivery_order_wise2}:
|
||||
name: outgoing picking 2 (wise unit)
|
||||
partner_id: base.res_partner_4
|
||||
picking_type_id: stock.picking_type_out
|
||||
move_lines:
|
||||
- product_id: product_wise
|
||||
product_uom_qty: 5.0
|
||||
location_id: stock_location_stock
|
||||
location_dest_id: stock_location_customers
|
||||
-
|
||||
Assign and confirm
|
||||
-
|
||||
!python {model: stock.picking, id: delivery_order_wise2}: |
|
||||
self.action_confirm()
|
||||
self.action_assign()
|
||||
assert self.state == 'assigned'
|
||||
-
|
||||
The operator is a wise guy and decides to do the opposite of what Odoo proposes. He uses the products reserved on picking 1 on picking 2 and vice versa
|
||||
-
|
||||
!python {model: stock.picking, id: False}: |
|
||||
picking1 = self.browse(ref('delivery_order_wise1'))
|
||||
picking2 = self.browse(ref('delivery_order_wise2'))
|
||||
move1 = picking1.move_lines[0]
|
||||
move2 = picking2.move_lines[0]
|
||||
pack_ids1 = picking1.move_line_ids
|
||||
pack_ids2 = picking2.move_line_ids
|
||||
|
||||
assert pack_ids1.location_id.id == ref('stock_location_14')
|
||||
assert set(pack_ids2.mapped('location_id.id')) == set([ref('stock_location_components'), ref('stock_location_14')])
|
||||
|
||||
# put the move lines from picking2 into picking1
|
||||
for pack_id2 in pack_ids2:
|
||||
new_pack_id1 = pack_id2.copy(default={'picking_id': picking1.id, 'move_id': move1.id})
|
||||
new_pack_id1.qty_done = new_pack_id1.product_qty
|
||||
new_pack_id1.with_context(bypass_reservation_update=True).product_uom_qty = 0
|
||||
|
||||
new_move_lines = picking1.move_line_ids.filtered(lambda p: p.qty_done)
|
||||
assert sum(new_move_lines.mapped('product_qty')) == 0
|
||||
assert sum(new_move_lines.mapped('qty_done')) == 5
|
||||
assert set(new_move_lines.mapped('location_id.id')) == set([ref('stock_location_components'), ref('stock_location_14')])
|
||||
|
||||
# put the move line from picking1 into picking2
|
||||
new_pack_id2 = pack_ids1.copy(default={'picking_id': picking2.id, 'move_id': move2.id})
|
||||
new_pack_id2.qty_done = new_pack_id2.product_qty
|
||||
new_pack_id2.with_context(bypass_reservation_update=True).product_uom_qty = 0
|
||||
|
||||
new_move_lines = picking2.move_line_ids.filtered(lambda p: p.qty_done)
|
||||
assert len(new_move_lines) == 1
|
||||
assert sum(new_move_lines.mapped('product_qty')) == 0
|
||||
assert sum(new_move_lines.mapped('qty_done')) == 5
|
||||
assert new_move_lines.location_id.id == ref('stock_location_14')
|
||||
-
|
||||
Process this picking
|
||||
-
|
||||
!python {model: stock.picking, id: delivery_order_wise1}: |
|
||||
self.do_transfer()
|
||||
-
|
||||
Check there was no negative quant created by this picking
|
||||
-
|
||||
!python {model: stock.quant, id: False}: |
|
||||
records = self.search([('product_id','=',ref('product_wise')), ('quantity', '<', 0.0), ('location_id.id', '=',
|
||||
ref('stock_location_stock'))])
|
||||
assert len(records.ids) == 0, 'This should not have created a negative quant'
|
||||
-
|
||||
Check the other delivery order has changed its state back to ready
|
||||
-
|
||||
!python {model: stock.picking, id: delivery_order_wise2}: |
|
||||
assert self.state == 'assigned', "Delivery order 2 should be back in ready state"
|
||||
-
|
||||
Process the second picking
|
||||
-
|
||||
!python {model: stock.picking, id: delivery_order_wise2}: |
|
||||
self.action_done()
|
||||
-
|
||||
Check all quants are in Customers and there are no negative quants anymore
|
||||
-
|
||||
!python {model: stock.quant, id: False}: |
|
||||
records = self.search([('product_id','=',ref('product_wise')), ('location_id', '!=', ref('stock_location_suppliers'))])
|
||||
assert all([x.location_id.id==ref('stock_location_customers') and x.quantity > 0.0 for x in records]), "Negative quant or wrong location detected"
|
||||
@@ -9,3 +9,7 @@ from . import test_quant
|
||||
from . import test_inventory
|
||||
from . import test_move
|
||||
from . import test_move2
|
||||
from . import test_packing
|
||||
from . import test_packing_neg
|
||||
from . import test_proc_rule
|
||||
from . import test_wise_operator
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestPacking(TransactionCase):
|
||||
|
||||
def test_packing(self):
|
||||
|
||||
# Create a new stockable product
|
||||
|
||||
product1 = self.env['product.product'].create({
|
||||
'name': 'Nice product',
|
||||
'type': 'product',
|
||||
'categ_id': self.ref('product.product_category_1'),
|
||||
'list_price': 100.0,
|
||||
'standard_price': 70.0,
|
||||
'seller_ids': [(0, 0, {
|
||||
'delay': 1,
|
||||
'name': self.ref('base.res_partner_2'),
|
||||
'min_qty': 2.0,
|
||||
})],
|
||||
'uom_id': self.ref('product.product_uom_unit'),
|
||||
'uom_po_id': self.ref('product.product_uom_unit'),
|
||||
})
|
||||
|
||||
# Create an incoming picking for this product of 300 PCE from suppliers to stock
|
||||
default_get_vals = self.env['stock.picking'].default_get(list(self.env['stock.picking'].fields_get()))
|
||||
default_get_vals.update({
|
||||
'name': 'Incoming picking',
|
||||
'partner_id': self.ref('base.res_partner_2'),
|
||||
'picking_type_id': self.ref('stock.picking_type_in'),
|
||||
'move_lines': [(0, 0, {
|
||||
'product_id': product1.id,
|
||||
'product_uom_qty': 300.00,
|
||||
'location_id': self.ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': self.ref('stock.stock_location_stock'),
|
||||
})],
|
||||
})
|
||||
pick1 = self.env['stock.picking'].new(default_get_vals)
|
||||
pick1.onchange_picking_type()
|
||||
pick1.move_lines.onchange_product_id()
|
||||
vals = pick1._convert_to_write(pick1._cache)
|
||||
pick1 = self.env['stock.picking'].create(vals)
|
||||
|
||||
# Confirm and assign picking
|
||||
pick1.action_confirm()
|
||||
pick1.action_assign()
|
||||
|
||||
# Put 120 pieces on Pallet 1 (package), 120 pieces on Pallet 2 with lot A and 60 pieces on Pallet 3
|
||||
lot_a = self.env['stock.production.lot'].create({
|
||||
'name': 'Lot A',
|
||||
'product_id': product1.id,
|
||||
})
|
||||
|
||||
# create package
|
||||
package1 = self.env['stock.quant.package'].create({'name': 'Pallet 1'})
|
||||
package2 = self.env['stock.quant.package'].create({'name': 'Pallet 2'})
|
||||
package3 = self.env['stock.quant.package'].create({'name': 'Pallet 3'})
|
||||
|
||||
# Create package for each line and assign it as result_package_id
|
||||
# create pack operation
|
||||
pick1.move_line_ids[0].write({'result_package_id': package1.id, 'qty_done': 120})
|
||||
new_pack1 = self.env['stock.move.line'].create({
|
||||
'product_id': product1.id,
|
||||
'product_uom_id': self.ref('product.product_uom_unit'),
|
||||
'picking_id': pick1.id,
|
||||
'lot_id': lot_a.id,
|
||||
'qty_done': 120.0,
|
||||
'result_package_id': package2.id,
|
||||
'location_id': self.ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': self.ref('stock.stock_location_stock')
|
||||
})
|
||||
new_pack2 = self.env['stock.move.line'].create({
|
||||
'product_id': product1.id,
|
||||
'product_uom_id': self.ref('product.product_uom_unit'),
|
||||
'picking_id': pick1.id,
|
||||
'result_package_id': package3.id,
|
||||
'qty_done': 60.0,
|
||||
'location_id': self.ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': self.ref('stock.stock_location_stock')
|
||||
})
|
||||
|
||||
# Transfer the receipt
|
||||
pick1.action_done()
|
||||
|
||||
# Check the system created 5 quants one with 120 pieces on pallet 1,
|
||||
# one with 120 pieces on pallet 2 with lot A and 60 pieces on pallet 3 in stock
|
||||
# and 1 with -180 pieces and another with -120 piece lot A in supplier
|
||||
quants = self.env['stock.quant'].search([('product_id', '=', product1.id)])
|
||||
self.assertTrue(len(quants.ids) == 5, "The number of quants created is not correct")
|
||||
for quant in quants:
|
||||
if quant.package_id.name == 'Pallet 1':
|
||||
self.assertTrue(quant.quantity == 120, "Should have 120 pieces on pallet 1")
|
||||
elif quant.package_id.name == 'Pallet 2':
|
||||
self.assertTrue(quant.quantity == 120, "Should have 120 pieces on pallet 2")
|
||||
elif quant.package_id.name == 'Pallet 3':
|
||||
self.assertTrue(quant.quantity == 60, "Should have 60 pieces on pallet 3")
|
||||
|
||||
# Check there is no backorder or extra moves created
|
||||
backorder = self.env['stock.picking'].search([('backorder_id', '=', pick1.id)])
|
||||
self.assertTrue(not backorder)
|
||||
|
||||
# Check extra moves created
|
||||
self.assertTrue(len(pick1.move_lines) == 1)
|
||||
|
||||
# Make a delivery order of 300 pieces to the customer
|
||||
default_get_vals = self.env['stock.picking'].default_get(list(self.env['stock.picking'].fields_get()))
|
||||
default_get_vals.update({
|
||||
'name': 'outgoing picking',
|
||||
'partner_id': self.ref('base.res_partner_4'),
|
||||
'picking_type_id': self.ref('stock.picking_type_out'),
|
||||
'move_lines': [(0, 0, {
|
||||
'product_id': product1.id,
|
||||
'product_uom_qty': 300.00,
|
||||
'location_id': self.ref('stock.stock_location_stock'),
|
||||
'location_dest_id': self.ref('stock.stock_location_customers'),
|
||||
})],
|
||||
})
|
||||
delivery_order1 = self.env['stock.picking'].new(default_get_vals)
|
||||
delivery_order1.onchange_picking_type()
|
||||
delivery_order1.move_lines.onchange_product_id()
|
||||
vals = delivery_order1._convert_to_write(delivery_order1._cache)
|
||||
delivery_order1 = self.env['stock.picking'].create(vals)
|
||||
|
||||
# Assign and confirm
|
||||
delivery_order1.action_confirm()
|
||||
delivery_order1.action_assign()
|
||||
|
||||
# Instead of doing the 300 pieces, you decide to take pallet 1 (do not
|
||||
# mention product in operation here) and 20 pieces from lot A and 10 pieces from pallet 3
|
||||
for rec in delivery_order1.move_line_ids:
|
||||
if rec.package_id.name == 'Pallet 1':
|
||||
rec.qty_done = 120
|
||||
rec.result_package_id = False
|
||||
if rec.package_id.name == 'Pallet 2':
|
||||
rec.qty_done = 20
|
||||
rec.result_package_id = False
|
||||
if rec.package_id.name == 'Pallet 3':
|
||||
rec.qty_done = 10
|
||||
rec.result_package_id = False
|
||||
|
||||
# Process this picking
|
||||
delivery_order1.action_done()
|
||||
|
||||
# Check the quants that you have 100 pieces of pallet 2 and 50 pieces of pallet 3 in stock
|
||||
records = self.env['stock.quant'].search([('product_id', '=', product1.id)])
|
||||
for rec in records:
|
||||
if rec.package_id.name == 'Pallet 2' and rec.location_id.id == self.ref('stock.stock_location_stock'):
|
||||
self.assertTrue(rec.quantity == 100, "Should have 100 pieces in stock on pallet 2, got " + str(rec.quantity))
|
||||
elif rec.package_id.name == 'Pallet 3' and rec.location_id.id == self.ref('stock.stock_location_stock'):
|
||||
self.assertTrue(rec.quantity == 50, "Should have 50 pieces in stock on pallet 3")
|
||||
else:
|
||||
self.assertTrue(rec.location_id.id != self.ref('stock.stock_location_stock'), "Unrecognized quant in stock")
|
||||
|
||||
# Check a backorder was created and on that backorder, prepare partial and process backorder
|
||||
backorders = self.env['stock.picking'].search([('backorder_id', '=', delivery_order1.id)])
|
||||
self.assertTrue(backorders, "Backorder should have been created")
|
||||
backorders.action_assign()
|
||||
picking = backorders[0]
|
||||
self.assertTrue(len(picking.move_line_ids) == 2, "Wrong number of pack operation")
|
||||
for pack_op in picking.move_line_ids:
|
||||
self.assertTrue(pack_op.package_id.name in ('Pallet 2', 'Pallet 3'), "Wrong pallet info in pack operation (%s found)" % (pack_op.package_id.name))
|
||||
if pack_op.package_id.name == 'Pallet 2':
|
||||
self.assertTrue(pack_op.product_qty == 100)
|
||||
pack_op.qty_done = 100
|
||||
elif pack_op.package_id.name == 'Pallet 3':
|
||||
self.assertTrue(pack_op.product_qty == 50)
|
||||
pack_op.qty_done = 50
|
||||
backorders.action_done()
|
||||
|
||||
# Check there are still 0 pieces in stock
|
||||
records = self.env['stock.quant'].search([('product_id', '=', product1.id), ('location_id', '=', self.ref('stock.stock_location_stock'))])
|
||||
total_qty = 0
|
||||
for rec in records:
|
||||
total_qty += rec.quantity
|
||||
self.assertTrue(total_qty == 0, "Total quantity in stock should be 0 as the backorder took everything out of stock")
|
||||
self.assertTrue(product1.qty_available == 0, "Quantity available should be 0 too")
|
||||
@@ -0,0 +1,171 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestPackingNeg(TransactionCase):
|
||||
|
||||
def test_packing_neg(self):
|
||||
|
||||
# Create a new "negative" stockable product
|
||||
product_neg = self.env['product.product'].create({
|
||||
'name': 'Negative product',
|
||||
'type': 'product',
|
||||
'categ_id': self.ref('product.product_category_1'),
|
||||
'list_price': 100.0,
|
||||
'standard_price': 70.0,
|
||||
'seller_ids': [(0, 0, {
|
||||
'delay': 1,
|
||||
'name': self.ref('base.res_partner_2'),
|
||||
'min_qty': 2.0,})],
|
||||
'uom_id': self.ref('product.product_uom_unit'),
|
||||
'uom_po_id': self.ref('product.product_uom_unit'),
|
||||
})
|
||||
|
||||
# Create an incoming picking for this product of 300 PCE from suppliers to stock
|
||||
default_get_vals = self.env['stock.picking'].default_get(list(self.env['stock.picking'].fields_get()))
|
||||
default_get_vals.update({
|
||||
'name': 'Incoming picking (negative product)',
|
||||
'partner_id': self.ref('base.res_partner_2'),
|
||||
'picking_type_id': self.ref('stock.picking_type_in'),
|
||||
'move_lines': [(0, 0, {
|
||||
'product_id': product_neg.id,
|
||||
'product_uom_qty': 300.00,
|
||||
'location_id': self.ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': self.ref('stock.stock_location_stock'),
|
||||
})],
|
||||
})
|
||||
pick_neg = self.env['stock.picking'].new(default_get_vals)
|
||||
pick_neg.onchange_picking_type()
|
||||
pick_neg.move_lines.onchange_product_id()
|
||||
vals = pick_neg._convert_to_write(pick_neg._cache)
|
||||
pick_neg = self.env['stock.picking'].create(vals)
|
||||
|
||||
# Confirm and assign picking
|
||||
pick_neg.action_confirm()
|
||||
pick_neg.action_assign()
|
||||
|
||||
# Put 120 pieces on Palneg 1 (package), 120 pieces on Palneg 2 with lot A and 60 pieces on Palneg 3
|
||||
# create lot A
|
||||
lot_a = self.env['stock.production.lot'].create({'name': 'Lot neg', 'product_id': product_neg.id})
|
||||
# create package
|
||||
package1 = self.env['stock.quant.package'].create({'name': 'Palneg 1'})
|
||||
package2 = self.env['stock.quant.package'].create({'name': 'Palneg 2'})
|
||||
package3 = self.env['stock.quant.package'].create({'name': 'Palneg 3'})
|
||||
# Create package for each line and assign it as result_package_id
|
||||
# create pack operation
|
||||
pick_neg.move_line_ids[0].write({'result_package_id': package1.id, 'qty_done': 120})
|
||||
new_pack1 = self.env['stock.move.line'].create({
|
||||
'product_id': product_neg.id,
|
||||
'product_uom_id': self.ref('product.product_uom_unit'),
|
||||
'picking_id': pick_neg.id,
|
||||
'lot_id': lot_a.id,
|
||||
'qty_done': 120,
|
||||
'result_package_id': package2.id,
|
||||
'location_id': self.ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': self.ref('stock.stock_location_stock')
|
||||
})
|
||||
new_pack2 = self.env['stock.move.line'].create({
|
||||
'product_id': product_neg.id,
|
||||
'product_uom_id': self.ref('product.product_uom_unit'),
|
||||
'picking_id': pick_neg.id,
|
||||
'result_package_id': package3.id,
|
||||
'qty_done': 60,
|
||||
'location_id': self.ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': self.ref('stock.stock_location_stock')
|
||||
})
|
||||
|
||||
# Transfer the receipt
|
||||
pick_neg.do_transfer()
|
||||
|
||||
# Make a delivery order of 300 pieces to the customer
|
||||
default_get_vals = self.env['stock.picking'].default_get(list(self.env['stock.picking'].fields_get()))
|
||||
default_get_vals.update({
|
||||
'name': 'outgoing picking (negative product)',
|
||||
'partner_id': self.ref('base.res_partner_4'),
|
||||
'picking_type_id': self.ref('stock.picking_type_out'),
|
||||
'move_lines': [(0, 0, {
|
||||
'product_id': product_neg.id,
|
||||
'product_uom_qty': 300.00,
|
||||
'location_id': self.ref('stock.stock_location_stock'),
|
||||
'location_dest_id': self.ref('stock.stock_location_customers'),
|
||||
})],
|
||||
})
|
||||
delivery_order_neg = self.env['stock.picking'].new(default_get_vals)
|
||||
delivery_order_neg.onchange_picking_type()
|
||||
delivery_order_neg.move_lines.onchange_product_id()
|
||||
vals = delivery_order_neg._convert_to_write(delivery_order_neg._cache)
|
||||
delivery_order_neg = self.env['stock.picking'].create(vals)
|
||||
|
||||
# Assign and confirm
|
||||
delivery_order_neg.action_confirm()
|
||||
delivery_order_neg.action_assign()
|
||||
|
||||
# Instead of doing the 300 pieces, you decide to take pallet 1 (do not mention
|
||||
# product in operation here) and 140 pieces from lot A/pallet 2 and 10 pieces from pallet 3
|
||||
|
||||
for rec in delivery_order_neg.move_line_ids:
|
||||
if rec.package_id.name == 'Palneg 1':
|
||||
rec.qty_done = rec.product_qty
|
||||
rec.result_package_id = False
|
||||
elif rec.package_id.name == 'Palneg 2' and rec.lot_id.name == 'Lot neg':
|
||||
rec.write({
|
||||
'qty_done': 140,
|
||||
'result_package_id': False,
|
||||
})
|
||||
elif rec.package_id.name == 'Palneg 3':
|
||||
rec.qty_done = 10
|
||||
rec.result_package_id = False
|
||||
|
||||
# Process this picking
|
||||
delivery_order_neg.do_transfer()
|
||||
|
||||
# Check the quants that you have -20 pieces pallet 2 in stock, and a total quantity
|
||||
# of 50 in stock from pallet 3 (should be 20+30, as it has been split by reservation)
|
||||
records = self.env['stock.quant'].search([('product_id', '=', product_neg.id)])
|
||||
pallet_3_stock_qty = 0
|
||||
for rec in records:
|
||||
if rec.package_id.name == 'Palneg 2' and rec.location_id.id == self.ref('stock.stock_location_stock'):
|
||||
self.assertTrue(rec.quantity == -20, "Should have -20 pieces in stock on pallet 2. Got " + str(rec.quantity))
|
||||
self.assertTrue(rec.lot_id.name == 'Lot neg', "It should have kept its Lot")
|
||||
elif rec.package_id.name == 'Palneg 3' and rec.location_id.id == self.ref('stock.stock_location_stock'):
|
||||
pallet_3_stock_qty += rec.quantity
|
||||
else:
|
||||
self.assertTrue(rec.location_id.id != self.ref('stock.stock_location_stock'), "Unrecognized quant in stock")
|
||||
self.assertEqual(pallet_3_stock_qty, 50, "Should have 50 pieces in stock on pallet 3")
|
||||
|
||||
# Create a picking for reconciling the negative quant
|
||||
default_get_vals = self.env['stock.picking'].default_get(list(self.env['stock.picking'].fields_get()))
|
||||
default_get_vals.update({
|
||||
'name': 'reconciling_delivery',
|
||||
'partner_id': self.ref('base.res_partner_4'),
|
||||
'picking_type_id': self.ref('stock.picking_type_in'),
|
||||
'move_lines': [(0, 0, {
|
||||
'product_id': product_neg.id,
|
||||
'product_uom_qty': 20.0,
|
||||
'location_id': self.ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': self.ref('stock.stock_location_stock'),
|
||||
})],
|
||||
})
|
||||
delivery_reconcile = self.env['stock.picking'].new(default_get_vals)
|
||||
delivery_reconcile.onchange_picking_type()
|
||||
delivery_reconcile.move_lines.onchange_product_id()
|
||||
vals = delivery_reconcile._convert_to_write(delivery_reconcile._cache)
|
||||
delivery_reconcile = self.env['stock.picking'].create(vals)
|
||||
|
||||
# Receive 20 products with lot neg in stock with a new incoming shipment that should be on pallet 2
|
||||
delivery_reconcile.action_confirm()
|
||||
lot = self.env["stock.production.lot"].search([
|
||||
('product_id', '=', product_neg.id),
|
||||
('name', '=', 'Lot neg')], limit=1)
|
||||
pack = self.env["stock.quant.package"].search([('name', '=', 'Palneg 2')], limit=1)
|
||||
delivery_reconcile.move_line_ids[0].write({'lot_id': lot.id, 'qty_done': 20.0, 'result_package_id': pack.id})
|
||||
delivery_reconcile.do_transfer()
|
||||
|
||||
# Check the negative quant was reconciled
|
||||
neg_quants = self.env['stock.quant'].search([
|
||||
('product_id', '=', product_neg.id),
|
||||
('quantity', '<', 0),
|
||||
('location_id.id', '!=', self.ref('stock.stock_location_suppliers'))])
|
||||
self.assertTrue(len(neg_quants) == 0, "Negative quants should have been reconciled")
|
||||
@@ -0,0 +1,69 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
class TestProcRule(TransactionCase):
|
||||
|
||||
def test_proc_rule(self):
|
||||
# Create a product route containing a procurement rule that will
|
||||
# generate a move from Stock for every procurement created in Output
|
||||
product_route = self.env['stock.location.route'].create({
|
||||
'name': 'Stock -> output route',
|
||||
'product_selectable': True,
|
||||
'pull_ids': [(0, 0, {
|
||||
'name': 'Stock -> output rule',
|
||||
'action': 'move',
|
||||
'picking_type_id': self.ref('stock.picking_type_internal'),
|
||||
'location_src_id': self.ref('stock.stock_location_stock'),
|
||||
'location_id': self.ref('stock.stock_location_output'),
|
||||
})],
|
||||
})
|
||||
|
||||
# Set this route on `product.product_product_3`
|
||||
self.env.ref('product.product_product_3').write({
|
||||
'route_ids': [(4, product_route.id)]})
|
||||
|
||||
# Create Delivery Order of 10 `product.product_product_3` from Output -> Customer
|
||||
default_get_vals = self.env['stock.picking'].default_get(list(self.env['stock.picking'].fields_get()))
|
||||
default_get_vals.update({
|
||||
'name': 'Delivery order for procurement',
|
||||
'partner_id': self.ref('base.res_partner_2'),
|
||||
'picking_type_id': self.ref('stock.picking_type_out'),
|
||||
'move_lines': [(0, 0, {
|
||||
'product_id': self.ref('product.product_product_3'),
|
||||
'product_uom_qty': 10.00,
|
||||
'procure_method': 'make_to_order',
|
||||
})],
|
||||
})
|
||||
pick_output = self.env['stock.picking'].new(default_get_vals)
|
||||
pick_output.onchange_picking_type()
|
||||
pick_output.move_lines.onchange_product_id()
|
||||
pick_output.update({
|
||||
'location_id': self.ref('stock.stock_location_output'),
|
||||
'location_dest_id': self.ref('stock.stock_location_customers'),
|
||||
})
|
||||
vals = pick_output._convert_to_write(pick_output._cache)
|
||||
pick_output = self.env['stock.picking'].create(vals)
|
||||
|
||||
# Confirm delivery order.
|
||||
pick_output.action_confirm()
|
||||
|
||||
# I run the scheduler.
|
||||
# Note: If purchase if already installed, the method _run_buy will be called due
|
||||
# to the purchase demo data. As we update the stock module to run this test, the
|
||||
# method won't be an attribute of stock.procurement at this moment. For that reason
|
||||
# we mute the logger when running the scheduler.
|
||||
with mute_logger('odoo.addons.stock.models.procurement'):
|
||||
self.env['procurement.group'].run_scheduler()
|
||||
|
||||
# Check that a picking was created from stock to output.
|
||||
moves = self.env['stock.move'].search([
|
||||
('product_id', '=', self.ref('product.product_product_3')),
|
||||
('location_id', '=', self.ref('stock.stock_location_stock')),
|
||||
('location_dest_id', '=', self.ref('stock.stock_location_output')),
|
||||
('move_dest_ids', 'in', [pick_output.move_lines[0].id])
|
||||
])
|
||||
self.assertEqual(len(moves.ids), 1, "It should have created a picking from Stock to Output with the original picking as destination")
|
||||
@@ -6,7 +6,6 @@ from odoo.addons.stock.tests.common2 import TestStockCommon
|
||||
class TestInventory(TestStockCommon):
|
||||
|
||||
def test_shipment(self):
|
||||
# TDE NOTE: this test replaces test/shipment.yml present until saas-10
|
||||
# TDE TODO
|
||||
# pickign.action_confirm -> confirm moves
|
||||
# picking.do_prepare_partial, should create pack ops, write on it ?
|
||||
|
||||
@@ -74,7 +74,6 @@ class TestWarehouse(TestStockCommon):
|
||||
# TDE TODO: expand this test
|
||||
|
||||
def test_basic_move(self):
|
||||
# TDE NOTE: replaces test/move.yml present until saas-10, including onchanges
|
||||
product = self.product_3.sudo(self.user_stock_manager)
|
||||
product.type = 'product'
|
||||
picking_out = self.env['stock.picking'].create({
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestWiseOperator(TransactionCase):
|
||||
|
||||
def test_wise_operator(self):
|
||||
|
||||
# Create a new stockable product
|
||||
product_wise = self.env['product.product'].create({
|
||||
'name': 'Wise Unit',
|
||||
'type': 'product',
|
||||
'categ_id': self.ref('product.product_category_1'),
|
||||
'uom_id': self.ref('product.product_uom_unit'),
|
||||
'uom_po_id': self.ref('product.product_uom_unit'),
|
||||
})
|
||||
|
||||
picking_default_vals = self.env['stock.picking'].default_get(list(self.env['stock.picking'].fields_get()))
|
||||
|
||||
# Create an incoming picking for this product of 10 PCE from suppliers to stock
|
||||
vals = dict(picking_default_vals, **{
|
||||
'name': 'Incoming picking (wise unit)',
|
||||
'partner_id': self.ref('base.res_partner_2'),
|
||||
'picking_type_id': self.ref('stock.picking_type_in'),
|
||||
'move_lines': [(0, 0, {
|
||||
'product_id': product_wise.id,
|
||||
'product_uom_qty': 10.00,
|
||||
'location_id': self.ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': self.ref('stock.stock_location_stock'),
|
||||
})],
|
||||
})
|
||||
pick1_wise = self.env['stock.picking'].new(vals)
|
||||
pick1_wise.onchange_picking_type()
|
||||
pick1_wise.move_lines.onchange_product_id()
|
||||
vals = pick1_wise._convert_to_write(pick1_wise._cache)
|
||||
pick1_wise = self.env['stock.picking'].create(vals)
|
||||
|
||||
# Confirm and assign picking and prepare partial
|
||||
pick1_wise.action_confirm()
|
||||
pick1_wise.action_assign()
|
||||
|
||||
# Put 4 pieces in shelf1 and 6 pieces in shelf2
|
||||
package1 = self.env['stock.quant.package'].create({'name': 'Pack 1'})
|
||||
pick1_wise.move_line_ids[0].write({
|
||||
'result_package_id': package1.id,
|
||||
'qty_done': 4,
|
||||
'location_dest_id': self.ref('stock.stock_location_components')
|
||||
})
|
||||
new_pack1 = self.env['stock.move.line'].create({
|
||||
'product_id': product_wise.id,
|
||||
'product_uom_id': self.ref('product.product_uom_unit'),
|
||||
'picking_id': pick1_wise.id,
|
||||
'qty_done': 6.0,
|
||||
'location_id': self.ref('stock.stock_location_suppliers'),
|
||||
'location_dest_id': self.ref('stock.stock_location_14')
|
||||
})
|
||||
|
||||
# Transfer the receipt
|
||||
pick1_wise.do_transfer()
|
||||
|
||||
# Check the system created 3 quants
|
||||
records = self.env['stock.quant'].search([('product_id', '=', product_wise.id)])
|
||||
self.assertEqual(len(records.ids), 3, "The number of quants created is not correct")
|
||||
|
||||
# Make a delivery order of 5 pieces to the customer
|
||||
vals = dict(picking_default_vals, **{
|
||||
'name': 'outgoing picking 1 (wise unit)',
|
||||
'partner_id': self.ref('base.res_partner_4'),
|
||||
'picking_type_id': self.ref('stock.picking_type_out'),
|
||||
'move_lines': [(0, 0, {
|
||||
'product_id': product_wise.id,
|
||||
'product_uom_qty': 5.0,
|
||||
'location_id': self.ref('stock.stock_location_stock'),
|
||||
'location_dest_id': self.ref('stock.stock_location_customers'),
|
||||
})],
|
||||
})
|
||||
delivery_order_wise1 = self.env['stock.picking'].new(vals)
|
||||
delivery_order_wise1.onchange_picking_type()
|
||||
delivery_order_wise1.move_lines.onchange_product_id()
|
||||
vals = delivery_order_wise1._convert_to_write(delivery_order_wise1._cache)
|
||||
delivery_order_wise1 = self.env['stock.picking'].create(vals)
|
||||
|
||||
# Assign and confirm
|
||||
delivery_order_wise1.action_confirm()
|
||||
delivery_order_wise1.action_assign()
|
||||
self.assertEqual(delivery_order_wise1.state, 'assigned')
|
||||
|
||||
# Make a delivery order of 5 pieces to the customer
|
||||
vals = dict(picking_default_vals, **{
|
||||
'name': 'outgoing picking 2 (wise unit)',
|
||||
'partner_id': self.ref('base.res_partner_4'),
|
||||
'picking_type_id': self.ref('stock.picking_type_out'),
|
||||
'move_lines': [(0, 0, {
|
||||
'product_id': product_wise.id,
|
||||
'product_uom_qty': 5.0,
|
||||
'location_id': self.ref('stock.stock_location_stock'),
|
||||
'location_dest_id': self.ref('stock.stock_location_customers'),
|
||||
})],
|
||||
})
|
||||
delivery_order_wise2 = self.env['stock.picking'].new(vals)
|
||||
delivery_order_wise2.onchange_picking_type()
|
||||
delivery_order_wise2.move_lines.onchange_product_id()
|
||||
vals = delivery_order_wise2._convert_to_write(delivery_order_wise2._cache)
|
||||
delivery_order_wise2 = self.env['stock.picking'].create(vals)
|
||||
|
||||
# Assign and confirm
|
||||
delivery_order_wise2.action_confirm()
|
||||
delivery_order_wise2.action_assign()
|
||||
self.assertEqual(delivery_order_wise2.state, 'assigned')
|
||||
|
||||
# The operator is a wise guy and decides to do the opposite of what Odoo proposes.
|
||||
# He uses the products reserved on picking 1 on picking 2 and vice versa
|
||||
move1 = delivery_order_wise1.move_lines[0]
|
||||
move2 = delivery_order_wise2.move_lines[0]
|
||||
pack_ids1 = delivery_order_wise1.move_line_ids
|
||||
pack_ids2 = delivery_order_wise2.move_line_ids
|
||||
|
||||
self.assertEqual(pack_ids1.location_id.id, self.ref('stock.stock_location_14'))
|
||||
self.assertEqual(set(pack_ids2.mapped('location_id.id')), set([
|
||||
self.ref('stock.stock_location_components'),
|
||||
self.ref('stock.stock_location_14')]))
|
||||
|
||||
# put the move lines from delivery_order_wise2 into delivery_order_wise1
|
||||
for pack_id2 in pack_ids2:
|
||||
new_pack_id1 = pack_id2.copy(default={'picking_id': delivery_order_wise1.id, 'move_id': move1.id})
|
||||
new_pack_id1.qty_done = new_pack_id1.product_qty
|
||||
new_pack_id1.with_context(bypass_reservation_update=True).product_uom_qty = 0
|
||||
|
||||
new_move_lines = delivery_order_wise1.move_line_ids.filtered(lambda p: p.qty_done)
|
||||
self.assertEqual(sum(new_move_lines.mapped('product_qty')), 0)
|
||||
self.assertEqual(sum(new_move_lines.mapped('qty_done')), 5)
|
||||
self.assertEqual(set(new_move_lines.mapped('location_id.id')), set([
|
||||
self.ref('stock.stock_location_components'),
|
||||
self.ref('stock.stock_location_14')]))
|
||||
|
||||
# put the move line from delivery_order_wise1 into delivery_order_wise2
|
||||
new_pack_id2 = pack_ids1.copy(default={'picking_id': delivery_order_wise2.id, 'move_id': move2.id})
|
||||
new_pack_id2.qty_done = new_pack_id2.product_qty
|
||||
new_pack_id2.with_context(bypass_reservation_update=True).product_uom_qty = 0
|
||||
|
||||
new_move_lines = delivery_order_wise2.move_line_ids.filtered(lambda p: p.qty_done)
|
||||
self.assertEqual(len(new_move_lines), 1)
|
||||
self.assertEqual(sum(new_move_lines.mapped('product_qty')), 0)
|
||||
self.assertEqual(sum(new_move_lines.mapped('qty_done')), 5)
|
||||
self.assertEqual(new_move_lines.location_id.id, self.ref('stock.stock_location_14'))
|
||||
|
||||
# Process this picking
|
||||
delivery_order_wise1.do_transfer()
|
||||
|
||||
# Check there was no negative quant created by this picking
|
||||
|
||||
records = self.env['stock.quant'].search([
|
||||
('product_id', '=', product_wise.id),
|
||||
('quantity', '<', 0.0),
|
||||
('location_id.id', '=', self.ref('stock.stock_location_stock'))])
|
||||
self.assertEqual(len(records.ids), 0, 'This should not have created a negative quant')
|
||||
|
||||
# Check the other delivery order has changed its state back to ready
|
||||
self.assertEqual(delivery_order_wise2.state, 'assigned', "Delivery order 2 should be back in ready state")
|
||||
|
||||
# Process the second picking
|
||||
delivery_order_wise2.action_done()
|
||||
|
||||
# Check all quants are in Customers and there are no negative quants anymore
|
||||
records = self.env['stock.quant'].search([
|
||||
('product_id', '=', product_wise.id),
|
||||
('location_id', '!=', self.ref('stock.stock_location_suppliers'))])
|
||||
self.assertTrue(all([x.location_id.id == self.ref('stock.stock_location_customers') and x.quantity > 0.0 for x in records]),
|
||||
"Negative quant or wrong location detected")
|
||||
@@ -3,3 +3,89 @@
|
||||
|
||||
from . import models
|
||||
from . import wizard
|
||||
|
||||
from odoo import api, SUPERUSER_ID, _, tools
|
||||
|
||||
def _configure_journals(cr, registry):
|
||||
"""Setting journal and property field (if needed)"""
|
||||
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
|
||||
# if we already have a coa installed, create journal and set property field
|
||||
company_ids = env['res.company'].search([('chart_template_id', '!=', False)])
|
||||
|
||||
for company_id in company_ids:
|
||||
# Check if property exists for stock account journal exists
|
||||
properties = env['ir.property'].search([
|
||||
('name', '=', 'property_stock_journal'),
|
||||
('company_id', '=', company_id.id)])
|
||||
|
||||
# If not, check if you can find a journal that is already there with the same name, otherwise create one
|
||||
if not properties:
|
||||
journal_id = env['account.journal'].search([
|
||||
('name', '=', _('Stock Journal')),
|
||||
('company_id', '=', company_id.id),
|
||||
('type', '=', 'general')], limit=1).id
|
||||
if not journal_id:
|
||||
journal_id = env['account.journal'].create({
|
||||
'name': _('Stock Journal'),
|
||||
'type': 'general',
|
||||
'code': 'STJ',
|
||||
'company_id': company_id.id,
|
||||
'show_on_dashboard': False
|
||||
}).id
|
||||
vals = {
|
||||
'name': 'property_stock_journal',
|
||||
'fields_id': env['ir.model.fields'].search([
|
||||
('name', '=', 'property_stock_journal'),
|
||||
('model', '=', 'product.category'),
|
||||
('relation', '=', 'account.journal')], limit=1).id,
|
||||
'company_id': company_id.id,
|
||||
'value': 'account.journal,' + str(journal_id)
|
||||
}
|
||||
env['ir.property'].create(vals)
|
||||
|
||||
# Property Stock Accounts
|
||||
todo_list = [
|
||||
'property_stock_account_input_categ_id',
|
||||
'property_stock_account_output_categ_id',
|
||||
'property_stock_valuation_account_id',
|
||||
]
|
||||
|
||||
for record in todo_list:
|
||||
account = getattr(company_id, record)
|
||||
value = account and 'account.account,' + str(account.id) or False
|
||||
if value:
|
||||
field_id = env['ir.model.fields'].search([
|
||||
('name', '=', record),
|
||||
('model', '=', 'product.category'),
|
||||
('relation', '=', 'account.account')
|
||||
], limit=1).id
|
||||
vals = {
|
||||
'name': record,
|
||||
'company_id': company_id.id,
|
||||
'fields_id': field_id,
|
||||
'value': value,
|
||||
'res_id': 'product.category,'+str(env.ref('product.product_category_all').id),
|
||||
}
|
||||
properties = env['ir.property'].search([
|
||||
('name', '=', record),
|
||||
('company_id', '=', company_id.id),
|
||||
('value_reference', '!=', False)])
|
||||
if not properties:
|
||||
# create the property
|
||||
env['ir.property'].create(vals)
|
||||
|
||||
if not tools.config['without_demo']:
|
||||
account_id = env['account.account'].search([('tag_ids', '=', env.ref('account.demo_stock_account').id)], limit=1).id
|
||||
fields_id = env['ir.model.fields'].search([('model', '=', 'product.category'), ('name', '=', 'property_stock_valuation_account_id')], limit=1).id
|
||||
if not account_id:
|
||||
account_id = env['account.account'].search([('user_type_id', '=', env.ref('account.data_account_type_current_assets').id)], limit=1).id
|
||||
if account_id:
|
||||
vals = {
|
||||
'name': 'property_stock_valuation_account_id',
|
||||
'fields_id': fields_id,
|
||||
'value': 'account.account,'+str(account_id),
|
||||
'company_id': env.ref('base.main_company').id,
|
||||
}
|
||||
env['ir.model.data']._update('ir.property', 'stock_account', vals, 'property_stock_valuation_account_id')
|
||||
|
||||
@@ -23,9 +23,6 @@ Dashboard / Reports for Warehouse Management includes:
|
||||
'depends': ['stock', 'account'],
|
||||
'category': 'Hidden',
|
||||
'sequence': 16,
|
||||
'demo': [
|
||||
'data/stock_account_demo.yml'
|
||||
],
|
||||
'data': [
|
||||
'security/stock_account_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
@@ -35,10 +32,10 @@ Dashboard / Reports for Warehouse Management includes:
|
||||
'views/res_config_settings_views.xml',
|
||||
'data/product_data.xml',
|
||||
'views/product_views.xml',
|
||||
'data/stock_account_data_post_install.yml',
|
||||
],
|
||||
'test': [
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
'post_init_hook': '_configure_journals',
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
-
|
||||
Setting journal and property field (if needed)
|
||||
-
|
||||
!python {model: account.journal, id: False} : |
|
||||
#if we already have a coa installed, create journal and set property field
|
||||
company_obj = self.env['res.company']
|
||||
company_ids = company_obj.search([('chart_template_id', '!=', False)]).ids
|
||||
for company_id in company_ids:
|
||||
from odoo.tools.translate import _
|
||||
|
||||
#Check if property exists for stock account journal exists
|
||||
PropertyObj = self.env['ir.property']
|
||||
properties = PropertyObj.search([('name', '=', 'property_stock_journal'), ('company_id', '=', company_id)])
|
||||
AccountJournal = self.env['account.journal']
|
||||
|
||||
#If not, check if you can find a journal that is already there with the same name, otherwise create one
|
||||
if not properties:
|
||||
journal_id = AccountJournal.search([('name', '=', _('Stock Journal')), ('company_id', '=', company_id), ('type', '=', 'general')], limit=1).id
|
||||
if not journal_id:
|
||||
journal_id = AccountJournal.create({
|
||||
'name': _('Stock Journal'),
|
||||
'type': 'general',
|
||||
'code': 'STJ',
|
||||
'company_id': company_id,
|
||||
'show_on_dashboard': False
|
||||
}).id
|
||||
vals = {'name': 'property_stock_journal',
|
||||
'fields_id': self.env['ir.model.fields'].search([
|
||||
('name', '=', 'property_stock_journal'),
|
||||
('model', '=', 'product.category'),
|
||||
('relation', '=', 'account.journal')], limit=1).id,
|
||||
'company_id': company_id,
|
||||
'value': 'account.journal,' + str(journal_id)}
|
||||
|
||||
PropertyObj.create(vals)
|
||||
|
||||
todo_list = [ # Property Stock Accounts
|
||||
'property_stock_account_input_categ_id',
|
||||
'property_stock_account_output_categ_id',
|
||||
'property_stock_valuation_account_id',
|
||||
]
|
||||
company = company_obj.browse(company_id)
|
||||
for record in todo_list:
|
||||
account = getattr(company, record)
|
||||
value = account and 'account.account,' + str(account.id) or False
|
||||
if value:
|
||||
field_id = self.env['ir.model.fields'].search([
|
||||
('name', '=', record),
|
||||
('model', '=', 'product.category'),
|
||||
('relation', '=', 'account.account')
|
||||
], limit=1).id
|
||||
vals = {
|
||||
'name': record,
|
||||
'company_id': company_id,
|
||||
'fields_id': field_id,
|
||||
'value': value,
|
||||
'res_id': 'product.category,'+str(ref('product.product_category_all')),
|
||||
}
|
||||
properties = PropertyObj.search([('name', '=', record), ('company_id', '=', company.id), ('value_reference', '!=', False)])
|
||||
if not properties:
|
||||
#create the property
|
||||
PropertyObj.create(vals)
|
||||
@@ -1,16 +0,0 @@
|
||||
-
|
||||
Setting property field
|
||||
-
|
||||
!python {model: ir.model.data, id: False} : |
|
||||
account_id = self.env['account.account'].search([('tag_ids', 'in', [ref('account.demo_stock_account')])], limit=1).id
|
||||
fields_id = self.env['ir.model.fields'].search([('model','=','product.category'),('name','=','property_stock_valuation_account_id')], limit=1).id
|
||||
if not account_id:
|
||||
account_id = self.env['account.account'].search([('user_type_id', '=', ref('account.data_account_type_current_assets'))], limit=1).id
|
||||
if account_id:
|
||||
vals = {
|
||||
'name': 'property_stock_valuation_account_id',
|
||||
'fields_id': fields_id,
|
||||
'value': 'account.account,'+str(account_id),
|
||||
'company_id': ref('base.main_company'),
|
||||
}
|
||||
self._update('ir.property', 'stock_account', vals, 'property_stock_valuation_account_id')
|
||||
@@ -23,12 +23,6 @@ This module allows you to easily add extra costs on pickings and decide the spli
|
||||
'views/stock_landed_cost_views.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
],
|
||||
'test': [
|
||||
'../account/test/account_minimal_test.xml',
|
||||
'../stock_account/test/stock_valuation_account.xml',
|
||||
'test/stock_landed_costs.yml',
|
||||
'test/stock_landed_costs_rounding.yml',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
-
|
||||
In order to test the landed costs feature of stock, I create a landed cost, confirm it and check its account move created
|
||||
-
|
||||
I create 2 products with different volume and gross weight and configure them for real_time valuation and fifo costing method
|
||||
-
|
||||
!record {model: product.product, id: product_landed_cost_1}:
|
||||
name: "LC product 1"
|
||||
cost_method: fifo
|
||||
valuation: real_time
|
||||
weight: 10
|
||||
volume: 1
|
||||
property_stock_account_input: o_expense
|
||||
property_stock_account_output: o_income
|
||||
-
|
||||
!record {model: product.product, id: product_landed_cost_2}:
|
||||
name: "LC product 2"
|
||||
cost_method: fifo
|
||||
valuation: real_time
|
||||
weight: 20
|
||||
volume: 1.5
|
||||
property_stock_account_input: o_expense
|
||||
property_stock_account_output: o_income
|
||||
-
|
||||
I create 2 picking moving those products
|
||||
-
|
||||
!record {model: stock.picking, id: picking_landed_cost_1}:
|
||||
name: 'LC_pick_1'
|
||||
picking_type_id: stock.picking_type_out
|
||||
move_lines:
|
||||
- name: move 1
|
||||
product_id: product_landed_cost_1
|
||||
product_uom_qty: 5
|
||||
product_uom: product.product_uom_unit
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
-
|
||||
Confirm and assign picking
|
||||
-
|
||||
!python {model: stock.picking, id: picking_landed_cost_1}: |
|
||||
self.env.user.company_id.anglo_saxon_accounting = True
|
||||
self.action_confirm()
|
||||
self.action_assign()
|
||||
self.move_lines.quantity_done = 5
|
||||
self.button_validate()
|
||||
-
|
||||
!record {model: stock.picking, id: picking_landed_cost_2}:
|
||||
name: 'LC_pick_2'
|
||||
picking_type_id: stock.picking_type_out
|
||||
move_lines:
|
||||
- name: move 2
|
||||
product_id: product_landed_cost_2
|
||||
product_uom_qty: 10
|
||||
product_uom: product.product_uom_unit
|
||||
location_id: stock.stock_location_stock
|
||||
location_dest_id: stock.stock_location_customers
|
||||
-
|
||||
Confirm and assign picking
|
||||
-
|
||||
!python {model: stock.picking, id: picking_landed_cost_2}: |
|
||||
self.action_confirm()
|
||||
self.action_assign()
|
||||
self.move_lines.quantity_done = 10
|
||||
self.button_validate()
|
||||
-
|
||||
I create a landed cost for those 2 pickings
|
||||
-
|
||||
!record {model: stock.landed.cost, id: stock_landed_cost_1}:
|
||||
picking_ids: [picking_landed_cost_1, picking_landed_cost_2]
|
||||
account_journal_id: expenses_journal
|
||||
cost_lines:
|
||||
- name: 'equal split'
|
||||
split_method: 'equal'
|
||||
price_unit: 10
|
||||
product_id: product.product_product_2
|
||||
- name: 'split by quantity'
|
||||
split_method: 'by_quantity'
|
||||
price_unit: 150
|
||||
product_id: product.product_product_2
|
||||
- name: 'split by weight'
|
||||
split_method: 'by_weight'
|
||||
price_unit: 250
|
||||
product_id: product.product_product_2
|
||||
- name: 'split by volume'
|
||||
split_method: 'by_volume'
|
||||
price_unit: 20
|
||||
product_id: product.product_product_2
|
||||
valuation_adjustment_lines: []
|
||||
-
|
||||
I compute the landed cost using Compute button
|
||||
-
|
||||
!python {model: stock.landed.cost, id: stock_landed_cost_1}: |
|
||||
self.compute_landed_cost()
|
||||
-
|
||||
I check the valuation adjustment lines
|
||||
-
|
||||
!python {model: stock.landed.cost, id: stock_landed_cost_1}: |
|
||||
for valuation in self.valuation_adjustment_lines:
|
||||
if valuation.cost_line_id.name == 'equal split':
|
||||
assert valuation.additional_landed_cost == 5
|
||||
elif valuation.cost_line_id.name == 'split by quantity' and valuation.move_id.name == "move 1":
|
||||
assert valuation.additional_landed_cost == 50
|
||||
elif valuation.cost_line_id.name == 'split by quantity' and valuation.move_id.name == "move 2":
|
||||
assert valuation.additional_landed_cost == 100
|
||||
elif valuation.cost_line_id.name == 'split by weight' and valuation.move_id.name == "move 1":
|
||||
assert valuation.additional_landed_cost == 50
|
||||
elif valuation.cost_line_id.name == 'split by weight' and valuation.move_id.name == "move 2":
|
||||
assert valuation.additional_landed_cost == 200
|
||||
elif valuation.cost_line_id.name == 'split by volume' and valuation.move_id.name == "move 1":
|
||||
assert valuation.additional_landed_cost == 5
|
||||
elif valuation.cost_line_id.name == 'split by volume' and valuation.move_id.name == "move 2":
|
||||
assert valuation.additional_landed_cost == 15
|
||||
else:
|
||||
raise 'unrecognized valuation adjustment line'
|
||||
-
|
||||
I confirm the landed cost
|
||||
-
|
||||
!python {model: stock.landed.cost, id: stock_landed_cost_1}: |
|
||||
self.button_validate()
|
||||
-
|
||||
I check that the landed cost is now "Closed" and that it has an accounting entry
|
||||
-
|
||||
!assert {model: stock.landed.cost, id: stock_landed_cost_1}:
|
||||
- state == 'done'
|
||||
- account_move_id
|
||||
- len(account_move_id.line_ids) == 48
|
||||
@@ -1,143 +0,0 @@
|
||||
-
|
||||
In order to test the rounding in landed costs feature of stock, I create 2 landed cost
|
||||
-
|
||||
Define undivisible units
|
||||
-
|
||||
!record {model: product.uom, id: product_uom_unit_round_1}:
|
||||
category_id: product.product_uom_categ_unit
|
||||
name: Undivisible Unit(s)
|
||||
factor: 1.0
|
||||
rounding: 1.0
|
||||
-
|
||||
I create 2 products with different cost prices and configure them for real_time valuation and real price costing method
|
||||
-
|
||||
!record {model: product.product, id: product_landed_cost_3}:
|
||||
name: "LC product 3"
|
||||
cost_method: fifo
|
||||
uom_id: product_uom_unit_round_1
|
||||
valuation: real_time
|
||||
property_stock_account_input: o_expense
|
||||
property_stock_account_output: o_income
|
||||
-
|
||||
!record {model: product.product, id: product_landed_cost_4}:
|
||||
name: "LC product 4"
|
||||
cost_method: fifo
|
||||
uom_id: product_uom_unit_round_1
|
||||
valuation: real_time
|
||||
property_stock_account_input: o_expense
|
||||
property_stock_account_output: o_income
|
||||
-
|
||||
I create 2 pickings moving those products
|
||||
-
|
||||
!record {model: stock.picking, id: picking_landed_cost_3}:
|
||||
name: 'LC_pick_3'
|
||||
picking_type_id: stock.picking_type_in
|
||||
move_lines:
|
||||
- name: move 3
|
||||
product_id: product_landed_cost_3
|
||||
product_uom_qty: 13
|
||||
product_uom: product_uom_unit_round_1
|
||||
location_id: stock.stock_location_customers
|
||||
location_dest_id: stock.stock_location_stock
|
||||
-
|
||||
!record {model: stock.picking, id: picking_landed_cost_4}:
|
||||
name: 'LC_pick_4'
|
||||
picking_type_id: stock.picking_type_in
|
||||
move_lines:
|
||||
- name: move 4
|
||||
product_id: product_landed_cost_4
|
||||
product_uom_qty: 1
|
||||
product_uom: product.product_uom_dozen
|
||||
location_id: stock.stock_location_customers
|
||||
location_dest_id: stock.stock_location_stock
|
||||
price_unit: !eval 17.00/12.00
|
||||
-
|
||||
We perform all the tests for LC_pick_3
|
||||
-
|
||||
I receive picking LC_pick_3, and check how many quants are created
|
||||
-
|
||||
!python {model: stock.picking, id: picking_landed_cost_3}: |
|
||||
self.move_lines.price_unit = 1.0
|
||||
|
||||
self.action_confirm()
|
||||
self.action_assign()
|
||||
self.action_done()
|
||||
-
|
||||
I create a landed cost for picking 3
|
||||
-
|
||||
!record {model: stock.landed.cost, id: stock_landed_cost_2}:
|
||||
picking_ids: [picking_landed_cost_3]
|
||||
account_journal_id: expenses_journal
|
||||
cost_lines:
|
||||
- name: 'equal split'
|
||||
split_method: 'equal'
|
||||
price_unit: 15
|
||||
product_id: product.product_product_1
|
||||
valuation_adjustment_lines: []
|
||||
-
|
||||
I compute the landed cost using Compute button
|
||||
-
|
||||
!python {model: stock.landed.cost, id: stock_landed_cost_2}: |
|
||||
self.compute_landed_cost()
|
||||
-
|
||||
I check the valuation adjustment lines
|
||||
-
|
||||
!python {model: stock.landed.cost, id: stock_landed_cost_2}: |
|
||||
for valuation in self.valuation_adjustment_lines:
|
||||
assert valuation.additional_landed_cost == 15
|
||||
-
|
||||
I confirm the landed cost
|
||||
-
|
||||
!python {model: stock.landed.cost, id: stock_landed_cost_2}: |
|
||||
self.button_validate()
|
||||
-
|
||||
I check that the landed cost is now "Closed" and that it has an accounting entry
|
||||
-
|
||||
!assert {model: stock.landed.cost, id: stock_landed_cost_2}:
|
||||
- state == 'done'
|
||||
- account_move_id
|
||||
-
|
||||
We perform all the tests for LC_pick_4
|
||||
-
|
||||
I receive picking LC_pick_4, and check how many quants are created
|
||||
-
|
||||
!python {model: stock.picking, id: picking_landed_cost_4}: |
|
||||
self.move_lines.price_unit = 17.0/12.0
|
||||
|
||||
self.action_confirm()
|
||||
self.action_assign()
|
||||
self.action_done()
|
||||
-
|
||||
I create a landed cost for picking 4
|
||||
-
|
||||
!record {model: stock.landed.cost, id: stock_landed_cost_3}:
|
||||
picking_ids: [picking_landed_cost_4]
|
||||
account_journal_id: expenses_journal
|
||||
cost_lines:
|
||||
- name: 'equal split'
|
||||
split_method: 'equal'
|
||||
price_unit: 11
|
||||
product_id: product.product_product_1
|
||||
valuation_adjustment_lines: []
|
||||
-
|
||||
I compute the landed cost using Compute button
|
||||
-
|
||||
!python {model: stock.landed.cost, id: stock_landed_cost_3}: |
|
||||
self.compute_landed_cost()
|
||||
-
|
||||
I check the valuation adjustment lines
|
||||
-
|
||||
!python {model: stock.landed.cost, id: stock_landed_cost_3}: |
|
||||
for valuation in self.valuation_adjustment_lines:
|
||||
assert valuation.additional_landed_cost == 11
|
||||
-
|
||||
I confirm the landed cost
|
||||
-
|
||||
!python {model: stock.landed.cost, id: stock_landed_cost_3}: |
|
||||
self.button_validate()
|
||||
-
|
||||
I check that the landed cost is now "Closed" and that it has an accounting entry
|
||||
-
|
||||
!assert {model: stock.landed.cost, id: stock_landed_cost_3}:
|
||||
- state == 'done'
|
||||
- account_move_id
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import test_stock_landed_costs
|
||||
from . import test_stock_landed_costs_purchase
|
||||
from . import test_stock_landed_costs_rounding
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import tools
|
||||
from odoo.addons.account.tests.account_test_classes import AccountingTestCase
|
||||
from odoo.modules.module import get_module_resource
|
||||
|
||||
|
||||
class TestStockLandedCostsCommon(AccountingTestCase):
|
||||
|
||||
def _load(self, module, *args):
|
||||
tools.convert_file(self.cr, 'stock_landed_costs',
|
||||
get_module_resource(module, *args),
|
||||
{}, 'init', False, 'test', self.registry._assertion_report)
|
||||
|
||||
def setUp(self):
|
||||
super(TestStockLandedCostsCommon, self).setUp()
|
||||
# Objects
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.stock_landed_costs.tests.common import TestStockLandedCostsCommon
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class TestStockLandedCosts(TestStockLandedCostsCommon):
|
||||
|
||||
def test_stock_landed_costs(self):
|
||||
# In order to test the landed costs feature of stock,
|
||||
# I create a landed cost, confirm it and check its account move created
|
||||
|
||||
self._load('account', 'test', 'account_minimal_test.xml')
|
||||
self._load('stock_account', 'test', 'stock_valuation_account.xml')
|
||||
|
||||
# I create 2 products with different volume and gross weight and configure
|
||||
# them for real_time valuation and fifo costing method
|
||||
product_landed_cost_1 = self.env['product.product'].create({
|
||||
'name': "LC product 1",
|
||||
'cost_method': 'fifo',
|
||||
'valuation': 'real_time',
|
||||
'weight': 10,
|
||||
'volume': 1,
|
||||
'property_stock_account_input': self.ref('stock_landed_costs.o_expense'),
|
||||
'property_stock_account_output': self.ref('stock_landed_costs.o_income'),
|
||||
})
|
||||
|
||||
product_landed_cost_2 = self.env['product.product'].create({
|
||||
'name': "LC product 2",
|
||||
'cost_method': 'fifo',
|
||||
'valuation': 'real_time',
|
||||
'weight': 20,
|
||||
'volume': 1.5,
|
||||
'property_stock_account_input': self.ref('stock_landed_costs.o_expense'),
|
||||
'property_stock_account_output': self.ref('stock_landed_costs.o_income'),
|
||||
})
|
||||
|
||||
picking_default_vals = self.env['stock.picking'].default_get(list(self.env['stock.picking'].fields_get()))
|
||||
|
||||
# I create 2 picking moving those products
|
||||
vals = dict(picking_default_vals, **{
|
||||
'name': 'LC_pick_1',
|
||||
'picking_type_id': self.ref('stock.picking_type_out'),
|
||||
'move_lines': [(0, 0, {
|
||||
'product_id': product_landed_cost_1.id,
|
||||
'product_uom_qty': 5,
|
||||
'product_uom': self.ref('product.product_uom_unit'),
|
||||
'location_id': self.ref('stock.stock_location_stock'),
|
||||
'location_dest_id': self.ref('stock.stock_location_customers'),
|
||||
})],
|
||||
})
|
||||
picking_landed_cost_1 = self.env['stock.picking'].new(vals)
|
||||
picking_landed_cost_1.onchange_picking_type()
|
||||
picking_landed_cost_1.move_lines.onchange_product_id()
|
||||
picking_landed_cost_1.move_lines.name = 'move 1'
|
||||
vals = picking_landed_cost_1._convert_to_write(picking_landed_cost_1._cache)
|
||||
picking_landed_cost_1 = self.env['stock.picking'].create(vals)
|
||||
|
||||
# Confirm and assign picking
|
||||
self.env.user.company_id.anglo_saxon_accounting = True
|
||||
picking_landed_cost_1.action_confirm()
|
||||
picking_landed_cost_1.action_assign()
|
||||
picking_landed_cost_1.move_lines.quantity_done = 5
|
||||
picking_landed_cost_1.button_validate()
|
||||
|
||||
vals = dict(picking_default_vals, **{
|
||||
'name': 'LC_pick_2',
|
||||
'picking_type_id': self.ref('stock.picking_type_out'),
|
||||
'move_lines': [(0, 0, {
|
||||
'product_id': product_landed_cost_2.id,
|
||||
'product_uom_qty': 10,
|
||||
'product_uom': self.ref('product.product_uom_unit'),
|
||||
'location_id': self.ref('stock.stock_location_stock'),
|
||||
'location_dest_id': self.ref('stock.stock_location_customers'),
|
||||
})],
|
||||
})
|
||||
picking_landed_cost_2 = self.env['stock.picking'].new(vals)
|
||||
picking_landed_cost_2.onchange_picking_type()
|
||||
picking_landed_cost_2.move_lines.onchange_product_id()
|
||||
picking_landed_cost_2.move_lines.name = 'move 2'
|
||||
vals = picking_landed_cost_2._convert_to_write(picking_landed_cost_2._cache)
|
||||
picking_landed_cost_2 = self.env['stock.picking'].create(vals)
|
||||
|
||||
# Confirm and assign picking
|
||||
picking_landed_cost_2.action_confirm()
|
||||
picking_landed_cost_2.action_assign()
|
||||
picking_landed_cost_2.move_lines.quantity_done = 10
|
||||
picking_landed_cost_2.button_validate()
|
||||
|
||||
# I create a landed cost for those 2 pickings
|
||||
default_vals = self.env['stock.landed.cost'].default_get(list(self.env['stock.landed.cost'].fields_get()))
|
||||
default_vals.update({
|
||||
'picking_ids': [picking_landed_cost_1.id, picking_landed_cost_2.id],
|
||||
'account_journal_id': self.ref('stock_landed_costs.expenses_journal'),
|
||||
'cost_lines': [
|
||||
(0, 0, {'product_id': self.ref('product.product_product_2')}),
|
||||
(0, 0, {'product_id': self.ref('product.product_product_2')}),
|
||||
(0, 0, {'product_id': self.ref('product.product_product_2')}),
|
||||
(0, 0, {'product_id': self.ref('product.product_product_2')})],
|
||||
'valuation_adjustment_lines': [],
|
||||
})
|
||||
cost_lines_values = {
|
||||
'name': ['equal split', 'split by quantity', 'split by weight', 'split by volume'],
|
||||
'split_method': ['equal', 'by_quantity', 'by_weight', 'by_volume'],
|
||||
'price_unit': [10, 150, 250, 20],
|
||||
}
|
||||
stock_landed_cost_1 = self.env['stock.landed.cost'].new(default_vals)
|
||||
for index, cost_line in enumerate(stock_landed_cost_1.cost_lines):
|
||||
cost_line.onchange_product_id()
|
||||
cost_line.name = cost_lines_values['name'][index]
|
||||
cost_line.split_method = cost_lines_values['split_method'][index]
|
||||
cost_line.price_unit = cost_lines_values['price_unit'][index]
|
||||
vals = stock_landed_cost_1._convert_to_write(stock_landed_cost_1._cache)
|
||||
stock_landed_cost_1 = self.env['stock.landed.cost'].create(vals)
|
||||
|
||||
# I compute the landed cost using Compute button
|
||||
stock_landed_cost_1.compute_landed_cost()
|
||||
|
||||
# I check the valuation adjustment lines
|
||||
for valuation in stock_landed_cost_1.valuation_adjustment_lines:
|
||||
if valuation.cost_line_id.name == 'equal split':
|
||||
self.assertEqual(valuation.additional_landed_cost, 5)
|
||||
elif valuation.cost_line_id.name == 'split by quantity' and valuation.move_id.name == "move 1":
|
||||
self.assertEqual(valuation.additional_landed_cost, 50)
|
||||
elif valuation.cost_line_id.name == 'split by quantity' and valuation.move_id.name == "move 2":
|
||||
self.assertEqual(valuation.additional_landed_cost, 100)
|
||||
elif valuation.cost_line_id.name == 'split by weight' and valuation.move_id.name == "move 1":
|
||||
self.assertEqual(valuation.additional_landed_cost, 50)
|
||||
elif valuation.cost_line_id.name == 'split by weight' and valuation.move_id.name == "move 2":
|
||||
self.assertEqual(valuation.additional_landed_cost, 200)
|
||||
elif valuation.cost_line_id.name == 'split by volume' and valuation.move_id.name == "move 1":
|
||||
self.assertEqual(valuation.additional_landed_cost, 5)
|
||||
elif valuation.cost_line_id.name == 'split by volume' and valuation.move_id.name == "move 2":
|
||||
self.assertEqual(valuation.additional_landed_cost, 15)
|
||||
else:
|
||||
raise ValidationError('unrecognized valuation adjustment line')
|
||||
|
||||
# I confirm the landed cost
|
||||
stock_landed_cost_1.button_validate()
|
||||
|
||||
# I check that the landed cost is now "Closed" and that it has an accounting entry
|
||||
self.assertEqual(stock_landed_cost_1.state, "done")
|
||||
self.assertTrue(stock_landed_cost_1.account_move_id)
|
||||
self.assertEqual(len(stock_landed_cost_1.account_move_id.line_ids), 48)
|
||||
@@ -0,0 +1,155 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.stock_landed_costs.tests.common import TestStockLandedCostsCommon
|
||||
|
||||
|
||||
class TestStockLandedCostsRounding(TestStockLandedCostsCommon):
|
||||
|
||||
def test_stock_landed_costs_rounding(self):
|
||||
# In order to test the rounding in landed costs feature of stock, I create 2 landed cost
|
||||
|
||||
self._load('account', 'test', 'account_minimal_test.xml')
|
||||
self._load('stock_account', 'test', 'stock_valuation_account.xml')
|
||||
|
||||
# Define undivisible units
|
||||
product_uom_unit_round_1 = self.env['product.uom'].create({
|
||||
'category_id': self.ref('product.product_uom_categ_unit'),
|
||||
'name': 'Undivisible Unit(s)',
|
||||
'factor': 1.0,
|
||||
'rounding': 1.0,
|
||||
})
|
||||
|
||||
# I create 2 products with different cost prices and configure them for real_time
|
||||
# valuation and real price costing method
|
||||
product_landed_cost_3 = self.env['product.product'].create({
|
||||
'name': "LC product 3",
|
||||
'cost_method': 'fifo',
|
||||
'uom_id': product_uom_unit_round_1.id,
|
||||
'valuation': 'real_time',
|
||||
'property_stock_account_input': self.ref('stock_landed_costs.o_expense'),
|
||||
'property_stock_account_output': self.ref('stock_landed_costs.o_income'),
|
||||
})
|
||||
|
||||
product_landed_cost_4 = self.env['product.product'].create({
|
||||
'name': "LC product 4",
|
||||
'cost_method': 'fifo',
|
||||
'uom_id': product_uom_unit_round_1.id,
|
||||
'valuation': 'real_time',
|
||||
'property_stock_account_input': self.ref('stock_landed_costs.o_expense'),
|
||||
'property_stock_account_output': self.ref('stock_landed_costs.o_income'),
|
||||
})
|
||||
|
||||
picking_default_vals = self.env['stock.picking'].default_get(list(self.env['stock.picking'].fields_get()))
|
||||
|
||||
# I create 2 pickings moving those products
|
||||
vals = dict(picking_default_vals, **{
|
||||
'name': 'LC_pick_3',
|
||||
'picking_type_id': self.ref('stock.picking_type_in'),
|
||||
'move_lines': [(0, 0, {
|
||||
'product_id': product_landed_cost_3.id,
|
||||
'product_uom_qty': 13,
|
||||
'product_uom': product_uom_unit_round_1.id,
|
||||
'location_id': self.ref('stock.stock_location_customers'),
|
||||
'location_dest_id': self.ref('stock.stock_location_stock'),
|
||||
})],
|
||||
})
|
||||
picking_landed_cost_3 = self.env['stock.picking'].new(vals)
|
||||
picking_landed_cost_3.onchange_picking_type()
|
||||
picking_landed_cost_3.move_lines.onchange_product_id()
|
||||
picking_landed_cost_3.move_lines.name = 'move 3'
|
||||
vals = picking_landed_cost_3._convert_to_write(picking_landed_cost_3._cache)
|
||||
picking_landed_cost_3 = self.env['stock.picking'].create(vals)
|
||||
|
||||
vals = dict(picking_default_vals, **{
|
||||
'name': 'LC_pick_4',
|
||||
'picking_type_id': self.ref('stock.picking_type_in'),
|
||||
'move_lines': [(0, 0, {
|
||||
'product_id': product_landed_cost_4.id,
|
||||
'product_uom_qty': 1,
|
||||
'product_uom': self.ref('product.product_uom_dozen'),
|
||||
'location_id': self.ref('stock.stock_location_customers'),
|
||||
'location_dest_id': self.ref('stock.stock_location_stock'),
|
||||
'price_unit': 17.00 / 12.00,
|
||||
})],
|
||||
})
|
||||
picking_landed_cost_4 = self.env['stock.picking'].new(vals)
|
||||
picking_landed_cost_4.onchange_picking_type()
|
||||
picking_landed_cost_4.move_lines.onchange_product_id()
|
||||
picking_landed_cost_4.move_lines.name = 'move 4'
|
||||
vals = picking_landed_cost_4._convert_to_write(picking_landed_cost_4._cache)
|
||||
picking_landed_cost_4 = self.env['stock.picking'].create(vals)
|
||||
|
||||
# We perform all the tests for LC_pick_3
|
||||
# I receive picking LC_pick_3, and check how many quants are created
|
||||
picking_landed_cost_3.move_lines.price_unit = 1.0
|
||||
picking_landed_cost_3.action_confirm()
|
||||
picking_landed_cost_3.action_assign()
|
||||
picking_landed_cost_3.action_done()
|
||||
|
||||
# I create a landed cost for picking 3
|
||||
default_vals = self.env['stock.landed.cost'].default_get(list(self.env['stock.landed.cost'].fields_get()))
|
||||
default_vals.update({
|
||||
'picking_ids': [picking_landed_cost_3.id],
|
||||
'account_journal_id': self.ref('stock_landed_costs.expenses_journal'),
|
||||
'cost_lines': [(0, 0, {'product_id': self.ref('product.product_product_1')})],
|
||||
'valuation_adjustment_lines': [],
|
||||
})
|
||||
stock_landed_cost_2 = self.env['stock.landed.cost'].new(default_vals)
|
||||
stock_landed_cost_2.cost_lines.onchange_product_id()
|
||||
stock_landed_cost_2.cost_lines.name = 'equal split'
|
||||
stock_landed_cost_2.cost_lines.split_method = 'equal'
|
||||
stock_landed_cost_2.cost_lines.price_unit = 15
|
||||
vals = stock_landed_cost_2._convert_to_write(stock_landed_cost_2._cache)
|
||||
stock_landed_cost_2 = self.env['stock.landed.cost'].create(vals)
|
||||
|
||||
# I compute the landed cost using Compute button
|
||||
stock_landed_cost_2.compute_landed_cost()
|
||||
|
||||
# I check the valuation adjustment lines
|
||||
for valuation in stock_landed_cost_2.valuation_adjustment_lines:
|
||||
self.assertEqual(valuation.additional_landed_cost, 15)
|
||||
|
||||
# I confirm the landed cost
|
||||
stock_landed_cost_2.button_validate()
|
||||
|
||||
# I check that the landed cost is now "Closed" and that it has an accounting entry
|
||||
self.assertEqual(stock_landed_cost_2.state, 'done')
|
||||
self.assertTrue(stock_landed_cost_2.account_move_id)
|
||||
|
||||
# We perform all the tests for LC_pick_4
|
||||
# I receive picking LC_pick_4, and check how many quants are created
|
||||
picking_landed_cost_4.move_lines.price_unit = 17.0/12.0
|
||||
picking_landed_cost_4.action_confirm()
|
||||
picking_landed_cost_4.action_assign()
|
||||
picking_landed_cost_4.action_done()
|
||||
|
||||
# I create a landed cost for picking 4
|
||||
default_vals = self.env['stock.landed.cost'].default_get(list(self.env['stock.landed.cost'].fields_get()))
|
||||
default_vals.update({
|
||||
'picking_ids': [picking_landed_cost_4.id],
|
||||
'account_journal_id': self.ref('stock_landed_costs.expenses_journal'),
|
||||
'cost_lines': [(0, 0, {'product_id': self.ref('product.product_product_1')})],
|
||||
'valuation_adjustment_lines': [],
|
||||
})
|
||||
stock_landed_cost_3 = self.env['stock.landed.cost'].new(default_vals)
|
||||
stock_landed_cost_3.cost_lines.onchange_product_id()
|
||||
stock_landed_cost_3.cost_lines.name = 'equal split'
|
||||
stock_landed_cost_3.cost_lines.split_method = 'equal'
|
||||
stock_landed_cost_3.cost_lines.price_unit = 11
|
||||
vals = stock_landed_cost_3._convert_to_write(stock_landed_cost_3._cache)
|
||||
stock_landed_cost_3 = self.env['stock.landed.cost'].create(vals)
|
||||
|
||||
# I compute the landed cost using Compute button
|
||||
stock_landed_cost_3.compute_landed_cost()
|
||||
|
||||
# I check the valuation adjustment lines
|
||||
for valuation in stock_landed_cost_3.valuation_adjustment_lines:
|
||||
self.assertEqual(valuation.additional_landed_cost, 11)
|
||||
|
||||
# I confirm the landed cost
|
||||
stock_landed_cost_3.button_validate()
|
||||
|
||||
# I check that the landed cost is now "Closed" and that it has an accounting entry
|
||||
self.assertEqual(stock_landed_cost_3.state, 'done')
|
||||
self.assertTrue(stock_landed_cost_3.account_move_id)
|
||||
@@ -8,8 +8,4 @@
|
||||
'data': ['ir.model.access.csv'],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'test': [
|
||||
'tests/test_import_reference.yml',
|
||||
'tests/test_import_menuitem.yml',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
- |
|
||||
YAML Import menuitem scenario:
|
||||
Check that !menuitem import works with YAML.
|
||||
|
||||
- |
|
||||
Given a standard three level menuitem structure including:
|
||||
|
||||
A top level menu item (no parent no action)
|
||||
-
|
||||
!menuitem {
|
||||
id: test_menu_top,
|
||||
name: "Test Menu Top",
|
||||
}
|
||||
- >
|
||||
An intermediary menu item (parent, but no action)
|
||||
-
|
||||
!menuitem {
|
||||
id: test_menu_sub,
|
||||
parent: test_menu_top,
|
||||
name: "Test Menu Sub",
|
||||
}
|
||||
|
||||
- >
|
||||
A leaf menu item (parent and action)
|
||||
-
|
||||
!menuitem {
|
||||
id: test_menu_action,
|
||||
name: "Test Menu Action",
|
||||
parent: test_menu_sub,
|
||||
action: base.open_module_tree,
|
||||
}
|
||||
|
||||
- >
|
||||
Another leaf menu item (parent and action) in another menu structure with
|
||||
dotted parent id
|
||||
-
|
||||
!menuitem {
|
||||
id: test_menu_action_2,
|
||||
name: "Test Menu Action 2",
|
||||
parent: base.menu_management,
|
||||
action: base.open_module_tree,
|
||||
sequence: 93,
|
||||
}
|
||||
|
||||
- >
|
||||
Then these menu items should be present and properly configured.
|
||||
-
|
||||
!assert { model: ir.ui.menu, id: test_menu_top, string: menu item "top" is properly configured }:
|
||||
- name == 'Test Menu Top'
|
||||
- parent_id.name == False
|
||||
- action == False
|
||||
-
|
||||
!assert { model: ir.ui.menu, id: test_menu_sub, string: menu item "sub" is properly configured }:
|
||||
- name == 'Test Menu Sub'
|
||||
- parent_id.name == 'Test Menu Top'
|
||||
- action == False
|
||||
-
|
||||
!assert { model: ir.ui.menu, id: test_menu_action, string: menu item "action" is properly configured }:
|
||||
- name == 'Test Menu Action'
|
||||
- parent_id.name == 'Test Menu Sub'
|
||||
- action.name == 'Apps'
|
||||
-
|
||||
!assert { model: ir.ui.menu, id: test_menu_action_2, string: menu item "action_2" is properly configured }:
|
||||
- name == 'Test Menu Action 2'
|
||||
- sequence == 93
|
||||
- parent_id.name == 'Apps'
|
||||
- action.name == 'Apps'
|
||||
@@ -1,44 +0,0 @@
|
||||
- |
|
||||
YAML Import reference scenario:
|
||||
Check that importing into a "reference" type field works with YAML using
|
||||
both implicit references and !ref.
|
||||
|
||||
- |
|
||||
Given records imitating menu entries similar to the following entry from the base module:
|
||||
<menuitem id="menu_module_tree" parent="menu_management" name="Apps"
|
||||
sequence="5" action="open_module_tree" />
|
||||
|
||||
taken from odoo/addons/base/module/module_view.xml
|
||||
|
||||
- >
|
||||
A Menu item using !record and implicit xml_id lookup and sequence cast:
|
||||
-
|
||||
!record { model: ir.ui.menu, id: test_menu_0 }:
|
||||
name: "Local Modules 0"
|
||||
parent_id: base.menu_management
|
||||
sequence: 90
|
||||
action: base.open_module_tree
|
||||
|
||||
- >
|
||||
A Menu item using !record and explicit !ref and sequence !eval
|
||||
-
|
||||
!record { model: ir.ui.menu, id: test_menu_1 }:
|
||||
name: "Local Modules 1"
|
||||
parent_id: !refid base.menu_management
|
||||
sequence: !eval 91
|
||||
action: !refid base.open_module_tree
|
||||
- >
|
||||
Then these menu items should be present, properly configured and
|
||||
pointing to the same action.
|
||||
-
|
||||
!assert { model: ir.ui.menu, id: test_menu_0, string: menu item 0 is properly configured }:
|
||||
- name == 'Local Modules 0'
|
||||
- sequence == 90
|
||||
- parent_id.name == 'Apps'
|
||||
- action.name == 'Apps'
|
||||
-
|
||||
!assert { model: ir.ui.menu, id: test_menu_1, string: menu item 1 is properly configured }:
|
||||
- name == 'Local Modules 1'
|
||||
- sequence == 91
|
||||
- parent_id.name == 'Apps'
|
||||
- action.name == 'Apps'
|
||||
@@ -65,7 +65,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
|
||||
files.append(f)
|
||||
if k.endswith('_xml') and not (k == 'init_xml' and not f.endswith('.xml')):
|
||||
# init_xml, update_xml and demo_xml are deprecated except
|
||||
# for the case of init_xml with yaml, csv and sql files as
|
||||
# for the case of init_xml with csv and sql files as
|
||||
# we can't specify noupdate for those file.
|
||||
correct_key = 'demo' if k.count('demo') else 'data'
|
||||
_logger.warning(
|
||||
|
||||
+1
-13
@@ -884,16 +884,6 @@ def _reexec(updated_modules=None):
|
||||
args.insert(0, exe)
|
||||
os.execv(sys.executable, args)
|
||||
|
||||
def load_test_file_yml(registry, test_file):
|
||||
with registry.cursor() as cr:
|
||||
odoo.tools.convert_yaml_import(cr, 'base', open(test_file, 'rb'), 'test', {}, 'init')
|
||||
if config['test_commit']:
|
||||
_logger.info('test %s has been commited', test_file)
|
||||
cr.commit()
|
||||
else:
|
||||
_logger.info('test %s has been rollbacked', test_file)
|
||||
cr.rollback()
|
||||
|
||||
def load_test_file_py(registry, test_file):
|
||||
# Locate python module based on its filename and run the tests
|
||||
test_path, _ = os.path.splitext(os.path.abspath(test_file))
|
||||
@@ -928,9 +918,7 @@ def preload_registries(dbnames):
|
||||
test_file = config['test_file']
|
||||
_logger.info('loading test file %s', test_file)
|
||||
with odoo.api.Environment.manage():
|
||||
if test_file.endswith('yml'):
|
||||
load_test_file_yml(registry, test_file)
|
||||
elif test_file.endswith('py'):
|
||||
if test_file.endswith('py'):
|
||||
load_test_file_py(registry, test_file)
|
||||
|
||||
# run post-install tests
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
'description': """A module to test translation import.""",
|
||||
'depends': ['base'],
|
||||
'data': ['view.xml'],
|
||||
'test': ['tests.yml'],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
-
|
||||
Load the french translation.
|
||||
-
|
||||
!python {model: ir.translation }: |
|
||||
import odoo
|
||||
odoo.tools.trans_load(cr, 'test_translation_import/i18n/fr.po', 'fr_FR', verbose=False)
|
||||
-
|
||||
Assert we have loaded the correct number of entries for the given source string.
|
||||
-
|
||||
!python {model: ir.translation }: |
|
||||
translations = self.search(
|
||||
[('src', '=', '1XBUO5PUYH2RYZSA1FTLRYS8SPCNU1UYXMEYMM25ASV7JC2KTJZQESZYRV9L8CGB')])
|
||||
assert len(translations) == 2, "2 entries are expected, got %s instead." % len(translations)
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import test_term_count
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ from .convert import *
|
||||
from .translate import *
|
||||
from .graph import graph
|
||||
from .image import *
|
||||
from .yaml_import import *
|
||||
from .sql import *
|
||||
from .float_utils import *
|
||||
from .mail import *
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
class assertion_report(object):
|
||||
"""
|
||||
Simple pair of success and failures counts (used to record YAML and XML
|
||||
Simple pair of success and failures counts (used to record XML
|
||||
`assert` tags as well as unittest tests outcome (in this case, not
|
||||
individual `assert`)).
|
||||
"""
|
||||
|
||||
@@ -154,13 +154,13 @@ class configmanager(object):
|
||||
# Testing Group
|
||||
group = optparse.OptionGroup(parser, "Testing Configuration")
|
||||
group.add_option("--test-file", dest="test_file", my_default=False,
|
||||
help="Launch a python or YML test file.")
|
||||
help="Launch a python test file.")
|
||||
group.add_option("--test-report-directory", dest="test_report_directory", my_default=False,
|
||||
help="If set, will save sample of all reports in this directory.")
|
||||
group.add_option("--test-enable", action="store_true", dest="test_enable",
|
||||
my_default=False, help="Enable YAML and unit tests.")
|
||||
my_default=False, help="Enable unit tests.")
|
||||
group.add_option("--test-commit", action="store_true", dest="test_commit",
|
||||
my_default=False, help="Commit database changes performed by YAML or XML tests.")
|
||||
my_default=False, help="Commit database changes performed by XML tests.")
|
||||
parser.add_option_group(group)
|
||||
|
||||
# Logging Group
|
||||
|
||||
@@ -19,7 +19,6 @@ from . import assertion_report, pycompat
|
||||
from .config import config
|
||||
from .misc import file_open, unquote, ustr, SKIPPED_ELEMENT_TYPES
|
||||
from .translate import _
|
||||
from .yaml_import import convert_yaml_import
|
||||
from odoo import SUPERUSER_ID
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -786,8 +785,6 @@ def convert_file(cr, module, filename, idref, mode='update', noupdate=False, kin
|
||||
convert_csv_import(cr, module, pathname, fp.read(), idref, mode, noupdate)
|
||||
elif ext == '.sql':
|
||||
convert_sql_import(cr, fp)
|
||||
elif ext == '.yml':
|
||||
convert_yaml_import(cr, module, fp, kind, idref, mode, noupdate, report)
|
||||
elif ext == '.xml':
|
||||
convert_xml_import(cr, module, fp, idref, mode, noupdate, report)
|
||||
elif ext == '.js':
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
""" Helper functions for reports testing.
|
||||
|
||||
Please /do not/ import this file by default, but only explicitly call it
|
||||
through the code of yaml tests.
|
||||
through the code of python tests.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
@@ -1,857 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
import re
|
||||
import time # used to eval time.strftime expressions
|
||||
import types
|
||||
|
||||
from lxml import etree
|
||||
import yaml
|
||||
|
||||
import odoo
|
||||
from . import assertion_report, pycompat, yaml_tag
|
||||
from .config import config
|
||||
from .misc import file_open, DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from odoo import SUPERUSER_ID
|
||||
|
||||
# YAML import needs both safe and unsafe eval, but let's
|
||||
# default to /safe/.
|
||||
unsafe_eval = eval
|
||||
from .safe_eval import safe_eval
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class YamlImportException(Exception):
|
||||
pass
|
||||
|
||||
class YamlImportAbortion(Exception):
|
||||
pass
|
||||
|
||||
def _is_yaml_mapping(node, tag_constructor):
|
||||
value = isinstance(node, dict) \
|
||||
and len(node) == 1 \
|
||||
and isinstance(next(iter(node)), tag_constructor)
|
||||
return value
|
||||
|
||||
def is_comment(node):
|
||||
return isinstance(node, pycompat.string_types)
|
||||
|
||||
def is_assert(node):
|
||||
return isinstance(node, yaml_tag.Assert) \
|
||||
or _is_yaml_mapping(node, yaml_tag.Assert)
|
||||
|
||||
def is_record(node):
|
||||
return _is_yaml_mapping(node, yaml_tag.Record)
|
||||
|
||||
def is_python(node):
|
||||
return _is_yaml_mapping(node, yaml_tag.Python)
|
||||
|
||||
def is_menuitem(node):
|
||||
return isinstance(node, yaml_tag.Menuitem) \
|
||||
or _is_yaml_mapping(node, yaml_tag.Menuitem)
|
||||
|
||||
def is_function(node):
|
||||
return isinstance(node, yaml_tag.Function) \
|
||||
or _is_yaml_mapping(node, yaml_tag.Function)
|
||||
|
||||
def is_report(node):
|
||||
return isinstance(node, yaml_tag.Report)
|
||||
|
||||
def is_act_window(node):
|
||||
return isinstance(node, yaml_tag.ActWindow)
|
||||
|
||||
def is_delete(node):
|
||||
return isinstance(node, yaml_tag.Delete)
|
||||
|
||||
def is_context(node):
|
||||
return isinstance(node, yaml_tag.Context)
|
||||
|
||||
def is_url(node):
|
||||
return isinstance(node, yaml_tag.Url)
|
||||
|
||||
def is_eval(node):
|
||||
return isinstance(node, yaml_tag.Eval)
|
||||
|
||||
def is_ref(node):
|
||||
return isinstance(node, yaml_tag.Ref) \
|
||||
or _is_yaml_mapping(node, yaml_tag.Ref)
|
||||
|
||||
def is_string(node):
|
||||
return isinstance(node, pycompat.string_types)
|
||||
|
||||
class RecordDictWrapper(dict):
|
||||
"""
|
||||
Used to pass a record as locals in eval:
|
||||
records do not strictly behave like dict, so we force them to.
|
||||
"""
|
||||
def __init__(self, record):
|
||||
self.record = record
|
||||
def __getitem__(self, key):
|
||||
if key in self.record:
|
||||
return self.record[key]
|
||||
return dict.__getitem__(self, key)
|
||||
|
||||
class YamlInterpreter(object):
|
||||
def __init__(self, cr, module, id_map, mode, filename, report=None, noupdate=False, loglevel=logging.DEBUG):
|
||||
self.cr = cr
|
||||
self.module = module
|
||||
self.id_map = id_map
|
||||
self.mode = mode
|
||||
self.filename = filename
|
||||
if report is None:
|
||||
report = assertion_report.assertion_report()
|
||||
self.assertion_report = report
|
||||
self.noupdate = noupdate
|
||||
self.loglevel = loglevel
|
||||
self.uid = SUPERUSER_ID
|
||||
self.context = {} # opererp context
|
||||
self.eval_context = {'ref': self.get_id,
|
||||
'_ref': self.get_id, # added '_ref' so that record['ref'] is possible
|
||||
'time': time,
|
||||
'datetime': datetime,
|
||||
'timedelta': timedelta}
|
||||
self.env = odoo.api.Environment(self.cr, self.uid, self.context)
|
||||
self.sudo_env = self.env
|
||||
|
||||
def _log(self, *args, **kwargs):
|
||||
_logger.log(self.loglevel, *args, **kwargs)
|
||||
|
||||
def validate_xml_id(self, xml_id):
|
||||
id = xml_id
|
||||
if '.' in xml_id:
|
||||
module, id = xml_id.split('.', 1)
|
||||
assert '.' not in id, "The ID reference '%s' must contain at most one dot.\n" \
|
||||
"It is used to refer to other modules ID, in the form: module.record_id" \
|
||||
% (xml_id,)
|
||||
if module != self.module:
|
||||
module_count = self.env['ir.module.module'].search_count([('name', '=', module), ('state', '=', 'installed')])
|
||||
assert module_count == 1, 'The ID "%s" refers to an uninstalled module.' % (xml_id,)
|
||||
|
||||
def get_id(self, xml_id):
|
||||
if xml_id is False or xml_id is None:
|
||||
return False
|
||||
#if not xml_id:
|
||||
# raise YamlImportException("The xml_id should be a non empty string.")
|
||||
elif isinstance(xml_id, int):
|
||||
id = xml_id
|
||||
elif xml_id in self.id_map:
|
||||
id = self.id_map[xml_id]
|
||||
else:
|
||||
full_xml_id = xml_id
|
||||
if '.' not in full_xml_id:
|
||||
full_xml_id = self.module + '.' + full_xml_id
|
||||
try:
|
||||
id = self.env.ref(full_xml_id).id
|
||||
self.id_map[xml_id] = id
|
||||
except ValueError:
|
||||
raise ValueError("""%r not found when processing %s.
|
||||
This Yaml file appears to depend on missing data. This often happens for
|
||||
tests that belong to a module's test suite and depend on each other.""" % (xml_id, self.filename))
|
||||
|
||||
return id
|
||||
|
||||
def get_record(self, xml_id):
|
||||
if '.' not in xml_id:
|
||||
xml_id = "%s.%s" % (self.module, xml_id)
|
||||
return self.env.ref(xml_id)
|
||||
|
||||
def get_context(self, node, eval_dict):
|
||||
context = self.context.copy()
|
||||
if node.context:
|
||||
context.update(safe_eval(node.context, eval_dict))
|
||||
return context
|
||||
|
||||
def isnoupdate(self, node):
|
||||
return self.noupdate or node.noupdate or False
|
||||
|
||||
def _get_first_result(self, results, default=False):
|
||||
if len(results):
|
||||
value = results[0]
|
||||
if isinstance(value, tuple):
|
||||
value = value[0]
|
||||
else:
|
||||
value = default
|
||||
return value
|
||||
|
||||
def process_comment(self, node):
|
||||
return node
|
||||
|
||||
def _log_assert_failure(self, msg, *args):
|
||||
self.assertion_report.record_failure()
|
||||
_logger.error(msg, *args)
|
||||
|
||||
def _get_assertion_id(self, assertion):
|
||||
if assertion.id:
|
||||
ids = [self.get_id(assertion.id)]
|
||||
elif assertion.search:
|
||||
q = safe_eval(assertion.search, self.eval_context)
|
||||
ids = self.env(context=assertion.context)[assertion.model].search(q)
|
||||
else:
|
||||
raise YamlImportException('Nothing to assert: you must give either an id or a search criteria.')
|
||||
return ids
|
||||
|
||||
def process_assert(self, node):
|
||||
if isinstance(node, dict):
|
||||
assertion, expressions = list(node.items())[0]
|
||||
else:
|
||||
assertion, expressions = node, []
|
||||
|
||||
if self.isnoupdate(assertion) and self.mode != 'init':
|
||||
_logger.warning('This assertion was not evaluated ("%s").', assertion.string)
|
||||
return
|
||||
model = self.env[assertion.model]
|
||||
ids = self._get_assertion_id(assertion)
|
||||
if assertion.count is not None and len(ids) != assertion.count:
|
||||
msg = 'assertion "%s" failed!\n' \
|
||||
' Incorrect search count:\n' \
|
||||
' expected count: %d\n' \
|
||||
' obtained count: %d\n'
|
||||
args = (assertion.string, assertion.count, len(ids))
|
||||
self._log_assert_failure(msg, *args)
|
||||
else:
|
||||
context = self.get_context(assertion, self.eval_context)
|
||||
records = model.with_context(context).browse(ids)
|
||||
for record in records:
|
||||
for test in expressions:
|
||||
try:
|
||||
success = unsafe_eval(test, self.eval_context, RecordDictWrapper(record))
|
||||
except Exception as e:
|
||||
_logger.debug('Exception during evaluation of !assert block in yaml_file %s.', self.filename, exc_info=True)
|
||||
raise YamlImportAbortion(e)
|
||||
if not success:
|
||||
msg = 'Assertion "%s" FAILED\ntest: %s\n'
|
||||
args = (assertion.string, test)
|
||||
for aop in ('==', '!=', '<>', 'in', 'not in', '>=', '<=', '>', '<'):
|
||||
if aop in test:
|
||||
left, right = test.split(aop,1)
|
||||
lmsg = ''
|
||||
rmsg = ''
|
||||
try:
|
||||
lmsg = unsafe_eval(left, self.eval_context, RecordDictWrapper(record))
|
||||
except Exception as e:
|
||||
lmsg = '<exc>'
|
||||
|
||||
try:
|
||||
rmsg = unsafe_eval(right, self.eval_context, RecordDictWrapper(record))
|
||||
except Exception as e:
|
||||
rmsg = '<exc>'
|
||||
|
||||
msg += 'values: ! %s %s %s'
|
||||
args += ( lmsg, aop, rmsg )
|
||||
break
|
||||
|
||||
self._log_assert_failure(msg, *args)
|
||||
return
|
||||
else: # all tests were successful for this assertion tag (no break)
|
||||
self.assertion_report.record_success()
|
||||
|
||||
def _coerce_bool(self, value, default=False):
|
||||
if isinstance(value, bool):
|
||||
b = value
|
||||
if isinstance(value, str):
|
||||
b = value.strip().lower() not in ('0', 'false', 'off', 'no')
|
||||
elif isinstance(value, int):
|
||||
b = bool(value)
|
||||
else:
|
||||
b = default
|
||||
return b
|
||||
|
||||
def create_osv_memory_record(self, record, fields):
|
||||
model = self.env[record.model]
|
||||
context = self.get_context(record, self.eval_context)
|
||||
record_dict = self._create_record(model, fields, context=context)
|
||||
id_new = model.with_context(context).create(record_dict).id
|
||||
self.id_map[record.id] = int(id_new)
|
||||
return record_dict
|
||||
|
||||
def process_record(self, node):
|
||||
record, fields = list(node.items())[0]
|
||||
model = self.env[record.model]
|
||||
view_id = record.view
|
||||
if view_id and (view_id is not True) and isinstance(view_id, pycompat.string_types):
|
||||
if '.' not in view_id:
|
||||
view_id = self.module + '.' + view_id
|
||||
view_id = self.env.ref(view_id).id
|
||||
|
||||
if model.is_transient():
|
||||
record_dict=self.create_osv_memory_record(record, fields)
|
||||
else:
|
||||
self.validate_xml_id(record.id)
|
||||
module = self.module
|
||||
record_id = record.id
|
||||
if '.' in record_id:
|
||||
module, record_id = record_id.split('.',1)
|
||||
try:
|
||||
self.sudo_env['ir.model.data']._get_id(module, record_id)
|
||||
default = False
|
||||
except ValueError:
|
||||
default = True
|
||||
|
||||
if self.isnoupdate(record) and self.mode != 'init':
|
||||
id = self.sudo_env['ir.model.data']._update_dummy(record.model, module, record_id)
|
||||
# check if the resource already existed at the last update
|
||||
if id:
|
||||
self.id_map[record] = int(id)
|
||||
return None
|
||||
else:
|
||||
if not self._coerce_bool(record.forcecreate):
|
||||
return None
|
||||
|
||||
#context = self.get_context(record, self.eval_context)
|
||||
# FIXME: record.context like {'withoutemployee':True} should pass from self.eval_context. example: test_project.yml in project module
|
||||
# TODO: cleaner way to avoid resetting password in auth_signup (makes user creation costly)
|
||||
context = dict(record.context or {}, no_reset_password=True)
|
||||
env = self.env(user=SUPERUSER_ID, context=context)
|
||||
view_info = False
|
||||
if view_id:
|
||||
varg = view_id
|
||||
if view_id is True: varg = False
|
||||
view_info = model.with_env(env).fields_view_get(varg, 'form')
|
||||
|
||||
record_dict = self._create_record(model, fields, view_info, default=default, context=context)
|
||||
id = env['ir.model.data']._update(record.model, \
|
||||
module, record_dict, record_id, noupdate=self.isnoupdate(record), mode=self.mode)
|
||||
self.id_map[record.id] = int(id)
|
||||
if config.get('import_partial'):
|
||||
self.cr.commit()
|
||||
|
||||
def _create_record(self, model, fields, view_info=None, parent={}, default=True, context=None):
|
||||
"""This function processes the !record tag in yaml files. It simulates the record creation through an xml
|
||||
view (either specified on the !record tag or the default one for this object), including the calls to
|
||||
on_change() functions, and sending only values for fields that aren't set as readonly.
|
||||
:param model: model instance (new API)
|
||||
:param fields: dictionary mapping the field names and their values
|
||||
:param view_info: result of fields_view_get() called on the object
|
||||
:param parent: dictionary containing the values already computed for the parent, in case of one2many fields
|
||||
:param default: if True, the default values must be processed too or not
|
||||
:return: dictionary mapping the field names and their values, ready to use when calling the create() function
|
||||
:rtype: dict
|
||||
"""
|
||||
readonly_re = re.compile(r"""("readonly"|'readonly'): *true""")
|
||||
|
||||
class dotdict(object):
|
||||
""" Dictionary class that allow to access a dictionary value by using '.'.
|
||||
This is needed to eval correctly statements like 'parent.fieldname' in context.
|
||||
"""
|
||||
def __init__(self, d):
|
||||
self._dict = d
|
||||
def __getattr__(self, attr):
|
||||
return self._dict.get(attr, False)
|
||||
|
||||
def get_field_elems(view):
|
||||
""" return the field elements from a view as an OrderedDict """
|
||||
def traverse(node, elems):
|
||||
if node.tag == 'field':
|
||||
elems[node.get('name')] = node
|
||||
else:
|
||||
for child in node:
|
||||
traverse(child, elems)
|
||||
|
||||
elems = OrderedDict()
|
||||
arch = view['arch']
|
||||
if isinstance(arch, pycompat.text_type):
|
||||
arch = arch.encode('utf-8')
|
||||
traverse(etree.fromstring(arch), elems)
|
||||
return elems
|
||||
|
||||
def is_readonly(field_elem):
|
||||
""" return whether a given field is readonly """
|
||||
# TODO: currently we only support if readonly is True in modifiers.
|
||||
# Some improvement may be done in order to support modifiers like
|
||||
# {"readonly": [["state", "not in", ["draft", "confirm"]]]}
|
||||
return readonly_re.search(field_elem.get('modifiers', '{}'))
|
||||
|
||||
def get_2many_view(fg, field_name, view_type):
|
||||
""" return a view of the given type for the given field's comodel """
|
||||
fdesc = fg[field_name]
|
||||
return fdesc['views'].get(view_type) or \
|
||||
self.sudo_env[fdesc['relation']].fields_view_get(False, view_type)
|
||||
|
||||
def process_vals(fg, vals):
|
||||
""" sanitize the given field values """
|
||||
result = {}
|
||||
for field_name, field_value in vals.items():
|
||||
if field_name not in fg:
|
||||
continue
|
||||
if fg[field_name]['type'] == 'many2one' and isinstance(field_value, (tuple, list)):
|
||||
field_value = field_value[0]
|
||||
elif fg[field_name]['type'] in ('one2many', 'many2many'):
|
||||
# 2many fields: sanitize field values of sub-records
|
||||
sub_fg = get_2many_view(fg, field_name, 'form')['fields']
|
||||
def process(command):
|
||||
if isinstance(command, (tuple, list)) and command[0] in (0, 1):
|
||||
return (command[0], command[1], process_vals(sub_fg, command[2]))
|
||||
elif isinstance(command, dict):
|
||||
return process_vals(sub_fg, command)
|
||||
return command
|
||||
field_value = [process(v) for v in (field_value or [])]
|
||||
result[field_name] = field_value
|
||||
return result
|
||||
|
||||
def post_process(fg, elems, vals):
|
||||
""" filter out readonly fields from vals """
|
||||
result = {}
|
||||
for field_name, field_value in vals.items():
|
||||
if is_readonly(elems[field_name]):
|
||||
continue
|
||||
if fg[field_name]['type'] in ('one2many', 'many2many'):
|
||||
# 2many fields: filter field values of sub-records
|
||||
sub_view = get_2many_view(fg, field_name, 'form')
|
||||
sub_fg = sub_view['fields']
|
||||
sub_elems = get_field_elems(sub_view)
|
||||
def process(command):
|
||||
if isinstance(command, (tuple, list)) and command[0] in (0, 1):
|
||||
return (command[0], command[1], post_process(sub_fg, sub_elems, command[2]))
|
||||
elif isinstance(command, dict):
|
||||
return (0, 0, post_process(sub_fg, sub_elems, command))
|
||||
return command
|
||||
field_value = [process(v) for v in (field_value or [])]
|
||||
result[field_name] = field_value
|
||||
return result
|
||||
|
||||
context = context or {}
|
||||
fields = fields or {}
|
||||
parent_values = {context['field_parent']: parent} if context.get('field_parent') else {}
|
||||
|
||||
if view_info:
|
||||
fg = view_info['fields']
|
||||
elems = get_field_elems(view_info)
|
||||
recs = model.sudo().with_context(**context)
|
||||
onchange_spec = recs._onchange_spec(view_info)
|
||||
record_dict = {}
|
||||
|
||||
if default:
|
||||
# gather the default values on the object. (Can't use `fields´ as parameter instead of {} because we may
|
||||
# have references like `base.main_company´ in the yaml file and it's not compatible with the function)
|
||||
defaults = recs.sudo(self.uid)._add_missing_default_values({})
|
||||
|
||||
# copy the default values in record_dict, only if they are in the view (because that's what the client does)
|
||||
# the other default values will be added later on by the create(). The other fields in the view that haven't any
|
||||
# default value are set to False because we may have references to them in other field's context
|
||||
record_dict = dict.fromkeys(fg, False)
|
||||
record_dict.update(process_vals(fg, defaults))
|
||||
|
||||
# execute onchange on default values first
|
||||
default_names = [name for name in elems if name in record_dict]
|
||||
result = recs.onchange(dict(record_dict, **parent_values), default_names, onchange_spec)
|
||||
record_dict.update(process_vals(fg, result.get('value', {})))
|
||||
|
||||
# fill in fields, and execute onchange where necessary
|
||||
for field_name, field_elem in elems.items():
|
||||
assert field_name in fg, "The field '%s' is defined in the form view but not on the object '%s'!" % (field_name, model._name)
|
||||
if is_readonly(field_elem):
|
||||
# skip readonly fields
|
||||
continue
|
||||
|
||||
if field_name not in fields:
|
||||
continue
|
||||
|
||||
ctx = dict(context)
|
||||
form_view = view_info
|
||||
if fg[field_name]['type'] == 'one2many':
|
||||
# evaluate one2many fields using the inline form view defined in the parent
|
||||
form_view = get_2many_view(fg, field_name, 'form')
|
||||
ctx['field_parent'] = fg[field_name]['relation_field']
|
||||
if default and field_elem.get('context'):
|
||||
ctx.update(safe_eval(field_elem.get('context'),
|
||||
globals_dict={'parent': dotdict(parent)},
|
||||
locals_dict=record_dict))
|
||||
|
||||
field_value = self._eval_field(model, field_name, fields[field_name], form_view, parent=record_dict, default=default, context=ctx)
|
||||
record_dict.update(process_vals(fg, {field_name: field_value}))
|
||||
|
||||
# if field_name is given or has a default value, we evaluate its onchanges
|
||||
if not field_elem.attrib.get('on_change', False):
|
||||
continue
|
||||
|
||||
result = recs.onchange(dict(record_dict, **parent_values), field_name, onchange_spec)
|
||||
record_dict.update(process_vals(fg, {
|
||||
key: val
|
||||
for key, val in result.get('value', {}).items()
|
||||
if key not in fields # do not shadow values explicitly set in yaml
|
||||
}))
|
||||
|
||||
record_dict = post_process(fg, elems, record_dict)
|
||||
|
||||
else:
|
||||
record_dict = {}
|
||||
|
||||
for field_name, expression in fields.items():
|
||||
if record_dict.get(field_name):
|
||||
continue
|
||||
field_value = self._eval_field(model, field_name, expression, parent=record_dict, default=False, context=context)
|
||||
record_dict[field_name] = field_value
|
||||
|
||||
# filter returned values; indeed the last modification in the import process have added a default
|
||||
# value for all fields in the view; however some fields present in the view are not stored and
|
||||
# should not be sent to create. This bug appears with not stored function fields in the new API.
|
||||
return {
|
||||
key: val
|
||||
for key, val in record_dict.items()
|
||||
for field in [model._fields[key].base_field]
|
||||
if field.store or field.inverse
|
||||
}
|
||||
|
||||
def process_ref(self, node, field=None):
|
||||
assert node.search or node.id, '!ref node should have a `search` attribute or `id` attribute'
|
||||
if node.search:
|
||||
if node.model:
|
||||
model_name = node.model
|
||||
elif field:
|
||||
model_name = field.comodel_name
|
||||
else:
|
||||
raise YamlImportException('You need to give a model for the search, or a field to infer it.')
|
||||
model = self.env[model_name]
|
||||
q = safe_eval(node.search, self.eval_context)
|
||||
instances = model.search(q)
|
||||
if node.use:
|
||||
value = [inst[node.use] for inst in instances]
|
||||
else:
|
||||
value = instances.ids
|
||||
elif node.id:
|
||||
if field and field.type == 'reference':
|
||||
record = self.get_record(node.id)
|
||||
value = "%s,%s" % (record._name, record.id)
|
||||
else:
|
||||
value = self.get_id(node.id)
|
||||
else:
|
||||
value = None
|
||||
return value
|
||||
|
||||
def process_eval(self, node):
|
||||
return safe_eval(node.expression, self.eval_context)
|
||||
|
||||
def _eval_field(self, model, field_name, expression, view_info=False, parent={}, default=True, context=None):
|
||||
# TODO this should be refactored as something like model.get_field() in bin/osv
|
||||
if field_name not in model._fields:
|
||||
raise KeyError("Object '%s' does not contain field '%s'" % (model, field_name))
|
||||
field = model._fields[field_name]
|
||||
|
||||
if is_ref(expression):
|
||||
elements = self.process_ref(expression, field)
|
||||
if field.type in ("many2many", "one2many"):
|
||||
value = [(6, 0, elements)]
|
||||
else: # many2one or reference
|
||||
if isinstance(elements, (list,tuple)):
|
||||
value = self._get_first_result(elements)
|
||||
else:
|
||||
value = elements
|
||||
elif field.type == "many2one":
|
||||
value = self.get_id(expression)
|
||||
elif field.type == "one2many":
|
||||
comodel = self.env[field.comodel_name]
|
||||
value = [(0, 0, self._create_record(comodel, fields, view_info, parent=parent, default=default, context=context)) for fields in expression]
|
||||
elif field.type == "many2many":
|
||||
ids = [self.get_id(xml_id) for xml_id in expression]
|
||||
value = [(6, 0, ids)]
|
||||
elif field.type == "date" and is_string(expression):
|
||||
# enforce ISO format for string date values, to be locale-agnostic during tests
|
||||
time.strptime(expression, DEFAULT_SERVER_DATE_FORMAT)
|
||||
value = expression
|
||||
elif field.type == "datetime" and is_string(expression):
|
||||
# enforce ISO format for string datetime values, to be locale-agnostic during tests
|
||||
time.strptime(expression, DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
value = expression
|
||||
elif field.type == "reference":
|
||||
record = self.get_record(expression)
|
||||
value = "%s,%s" % (record._name, record.id)
|
||||
else: # scalar field
|
||||
if is_eval(expression):
|
||||
value = self.process_eval(expression)
|
||||
else:
|
||||
value = expression
|
||||
# raise YamlImportException('Unsupported field "%s" or value %s:%s' % (field_name, type(expression), expression))
|
||||
return value
|
||||
|
||||
def process_context(self, node):
|
||||
self.context = node.__dict__
|
||||
if node.uid:
|
||||
self.uid = self.get_id(node.uid)
|
||||
if node.noupdate:
|
||||
self.noupdate = node.noupdate
|
||||
self.env = odoo.api.Environment(self.cr, self.uid, self.context)
|
||||
self.sudo_env = self.env(user=SUPERUSER_ID)
|
||||
|
||||
def process_python(self, node):
|
||||
python, statements = list(node.items())[0]
|
||||
assert python.model or python.id, "!python node must have attribute `model` or `id`"
|
||||
if python.id is None:
|
||||
record = self.env[python.model]
|
||||
elif isinstance(python.id, pycompat.string_types):
|
||||
record = self.get_record(python.id)
|
||||
else:
|
||||
record = self.env[python.model].browse(python.id)
|
||||
if python.model:
|
||||
assert record._name == python.model, "`id` is not consistent with `model`"
|
||||
statements = "\n" * python.first_line + statements.replace("\r\n", "\n")
|
||||
code_context = {
|
||||
'self': record,
|
||||
'model': record,
|
||||
'cr': self.cr,
|
||||
'uid': self.uid,
|
||||
'log': self._log,
|
||||
'context': self.context,
|
||||
'openerp': odoo,
|
||||
}
|
||||
try:
|
||||
code_obj = compile(statements, self.filename, 'exec')
|
||||
unsafe_eval(code_obj, {'ref': self.get_id}, code_context)
|
||||
except AssertionError as e:
|
||||
self._log_assert_failure('AssertionError in Python code %s (line %d): %s',
|
||||
python.name, python.first_line, e)
|
||||
return
|
||||
except Exception as e:
|
||||
_logger.debug('Exception during evaluation of !python block in yaml_file %s.', self.filename, exc_info=True)
|
||||
raise
|
||||
else:
|
||||
self.assertion_report.record_success()
|
||||
|
||||
def _eval_params(self, model, params):
|
||||
args = []
|
||||
for i, param in enumerate(params):
|
||||
if isinstance(param, list):
|
||||
value = self._eval_params(model, param)
|
||||
elif is_ref(param):
|
||||
value = self.process_ref(param)
|
||||
elif is_eval(param):
|
||||
value = self.process_eval(param)
|
||||
elif isinstance(param, dict): # supports XML syntax
|
||||
param_model = self.env[param.get('model', model)]
|
||||
if 'search' in param:
|
||||
q = safe_eval(param['search'], self.eval_context)
|
||||
ids = param_model.search(q).ids
|
||||
value = self._get_first_result(ids)
|
||||
elif 'eval' in param:
|
||||
local_context = {'obj': param_model.browse}
|
||||
local_context.update(self.id_map)
|
||||
value = safe_eval(param['eval'], self.eval_context, local_context)
|
||||
else:
|
||||
raise YamlImportException('You must provide either a !ref or at least a "eval" or a "search" to function parameter #%d.' % i)
|
||||
else:
|
||||
value = param # scalar value
|
||||
args.append(value)
|
||||
return args
|
||||
|
||||
def process_function(self, node):
|
||||
function, params = list(node.items())[0]
|
||||
if self.isnoupdate(function) and self.mode != 'init':
|
||||
return
|
||||
model = self.env[function.model]
|
||||
if function.eval:
|
||||
args = self.process_eval(function.eval)
|
||||
else:
|
||||
args = self._eval_params(function.model, params)
|
||||
# this one still depends on the old API
|
||||
return odoo.api.call_kw(model, function.name, args, {})
|
||||
|
||||
def _set_group_values(self, node, values):
|
||||
if node.groups:
|
||||
group_names = node.groups.split(',')
|
||||
groups_value = []
|
||||
for group in group_names:
|
||||
if group.startswith('-'):
|
||||
group_id = self.get_id(group[1:])
|
||||
groups_value.append((3, group_id))
|
||||
else:
|
||||
group_id = self.get_id(group)
|
||||
groups_value.append((4, group_id))
|
||||
values['groups_id'] = groups_value
|
||||
|
||||
def process_menuitem(self, node):
|
||||
self.validate_xml_id(node.id)
|
||||
|
||||
if not node.parent:
|
||||
parent_id = False
|
||||
self.cr.execute('select id from ir_ui_menu where parent_id is null and name=%s', (node.name,))
|
||||
res = self.cr.fetchone()
|
||||
values = {'parent_id': parent_id, 'name': node.name}
|
||||
else:
|
||||
parent_id = self.get_id(node.parent)
|
||||
values = {'parent_id': parent_id}
|
||||
if node.name:
|
||||
values['name'] = node.name
|
||||
try:
|
||||
res = [ self.get_id(node.id) ]
|
||||
except: # which exception ?
|
||||
res = None
|
||||
|
||||
if node.action:
|
||||
action = self.get_record(node.action)
|
||||
values['action'] = '%s,%s' % (action._name, action.id)
|
||||
if not values.get('name'):
|
||||
values['name'] = action.name
|
||||
|
||||
if node.sequence:
|
||||
values['sequence'] = node.sequence
|
||||
|
||||
self._set_group_values(node, values)
|
||||
|
||||
pid = self.sudo_env['ir.model.data']._update('ir.ui.menu', self.module, values, node.id, \
|
||||
mode=self.mode, noupdate=self.isnoupdate(node), res_id=res and res[0] or False)
|
||||
|
||||
if node.id and pid:
|
||||
self.id_map[node.id] = int(pid)
|
||||
|
||||
def process_act_window(self, node):
|
||||
assert getattr(node, 'id'), "Attribute %s of act_window is empty !" % ('id',)
|
||||
assert getattr(node, 'name'), "Attribute %s of act_window is empty !" % ('name',)
|
||||
assert getattr(node, 'res_model'), "Attribute %s of act_window is empty !" % ('res_model',)
|
||||
self.validate_xml_id(node.id)
|
||||
view_id = False
|
||||
if node.view:
|
||||
view_id = self.get_id(node.view)
|
||||
if not node.context:
|
||||
node.context={}
|
||||
context = safe_eval(str(node.context), self.eval_context)
|
||||
values = {
|
||||
'name': node.name,
|
||||
'type': node.type or 'ir.actions.act_window',
|
||||
'view_id': view_id,
|
||||
'domain': node.domain,
|
||||
'context': context,
|
||||
'res_model': node.res_model,
|
||||
'src_model': node.src_model,
|
||||
'view_type': node.view_type or 'form',
|
||||
'view_mode': node.view_mode or 'tree,form',
|
||||
'usage': node.usage,
|
||||
'limit': node.limit,
|
||||
'multi': getattr(node, 'multi', False),
|
||||
}
|
||||
|
||||
self._set_group_values(node, values)
|
||||
|
||||
if node.target:
|
||||
values['target'] = node.target
|
||||
|
||||
if node.src_model and isinstance(node.src_model, basestring):
|
||||
values['binding_model_id'] = self.env['ir.model']._get(node.src_model).id
|
||||
|
||||
id = self.sudo_env['ir.model.data']._update('ir.actions.act_window', self.module, values, node.id, mode=self.mode)
|
||||
self.id_map[node.id] = int(id)
|
||||
# TODO add remove ir.model.data
|
||||
|
||||
def process_delete(self, node):
|
||||
assert getattr(node, 'model'), "Attribute %s of delete tag is empty !" % ('model',)
|
||||
if node.model in self.env:
|
||||
if node.search:
|
||||
records = self.env[node.model].search(safe_eval(node.search, self.eval_context))
|
||||
else:
|
||||
records = self.env[node.model].browse(self.get_id(node.id))
|
||||
if records:
|
||||
records.unlink()
|
||||
else:
|
||||
self._log("Record not deleted.")
|
||||
|
||||
def process_url(self, node):
|
||||
self.validate_xml_id(node.id)
|
||||
res = {'name': node.name, 'url': node.url, 'target': node.target}
|
||||
id = self.sudo_env['ir.model.data']._update("ir.actions.act_url", self.module, res, node.id, mode=self.mode)
|
||||
self.id_map[node.id] = int(id)
|
||||
# ir_set
|
||||
if (not node.menu or safe_eval(node.menu)) and id:
|
||||
action = self.env['ir.actions.actions'].browse(int(id))
|
||||
action.binding_model_id = self.env['ir.model']._get('ir.actions.act_url')
|
||||
|
||||
def process_report(self, node):
|
||||
values = {}
|
||||
for dest, f in (('name','string'), ('model','model'), ('report_name','name')):
|
||||
values[dest] = getattr(node, f)
|
||||
assert values[dest], "Attribute %s of report is empty !" % (f,)
|
||||
for field,dest in (('file', 'report_file'), ('attachment','attachment'),('attachment_use','attachment_use')):
|
||||
if getattr(node, field):
|
||||
values[dest] = getattr(node, field)
|
||||
values['multi'] = node.multi and safe_eval(node.multi)
|
||||
xml_id = node.id
|
||||
self.validate_xml_id(xml_id)
|
||||
|
||||
self._set_group_values(node, values)
|
||||
|
||||
id = self.sudo_env['ir.model.data']._update("ir.actions.report", \
|
||||
self.module, values, xml_id, noupdate=self.isnoupdate(node), mode=self.mode)
|
||||
self.id_map[xml_id] = int(id)
|
||||
|
||||
if not node.menu or safe_eval(node.menu):
|
||||
report = self.env['ir.actions.report'].browse(id)
|
||||
report.create_action()
|
||||
|
||||
def process_none(self):
|
||||
"""
|
||||
Empty node or commented node should not pass silently.
|
||||
"""
|
||||
self._log_assert_failure("You have an empty block in your tests.")
|
||||
|
||||
|
||||
def process(self, yaml_string):
|
||||
"""
|
||||
Processes a Yaml string. Custom tags are interpreted by 'process_' instance methods.
|
||||
"""
|
||||
yaml_tag.add_constructors()
|
||||
|
||||
is_preceded_by_comment = False
|
||||
for node in yaml.load(yaml_string):
|
||||
is_preceded_by_comment = self._log_node(node, is_preceded_by_comment)
|
||||
try:
|
||||
self._process_node(node)
|
||||
except Exception as e:
|
||||
_logger.exception(e)
|
||||
raise
|
||||
|
||||
def _process_node(self, node):
|
||||
if is_comment(node):
|
||||
self.process_comment(node)
|
||||
elif is_assert(node):
|
||||
self.process_assert(node)
|
||||
elif is_record(node):
|
||||
self.process_record(node)
|
||||
elif is_python(node):
|
||||
self.process_python(node)
|
||||
elif is_menuitem(node):
|
||||
self.process_menuitem(node)
|
||||
elif is_delete(node):
|
||||
self.process_delete(node)
|
||||
elif is_url(node):
|
||||
self.process_url(node)
|
||||
elif is_context(node):
|
||||
self.process_context(node)
|
||||
elif is_act_window(node):
|
||||
self.process_act_window(node)
|
||||
elif is_report(node):
|
||||
self.process_report(node)
|
||||
elif is_function(node):
|
||||
if isinstance(node, dict):
|
||||
self.process_function(node)
|
||||
else:
|
||||
self.process_function({node: []})
|
||||
elif node is None:
|
||||
self.process_none()
|
||||
else:
|
||||
raise YamlImportException("Can not process YAML block: %s" % node)
|
||||
|
||||
def _log_node(self, node, is_preceded_by_comment):
|
||||
if is_comment(node):
|
||||
is_preceded_by_comment = True
|
||||
self._log(node)
|
||||
elif not is_preceded_by_comment:
|
||||
if isinstance(node, dict):
|
||||
msg = "Creating %s\n with %s"
|
||||
args = list(node.items())[0]
|
||||
self._log(msg, *args)
|
||||
else:
|
||||
self._log(node)
|
||||
else:
|
||||
is_preceded_by_comment = False
|
||||
return is_preceded_by_comment
|
||||
|
||||
def yaml_import(cr, module, yamlfile, kind, idref=None, mode='init', noupdate=False, report=None):
|
||||
if idref is None:
|
||||
idref = {}
|
||||
loglevel = logging.DEBUG
|
||||
yaml_string = yamlfile.read()
|
||||
yaml_interpreter = YamlInterpreter(cr, module, idref, mode, filename=yamlfile.name, report=report, noupdate=noupdate, loglevel=loglevel)
|
||||
yaml_interpreter.process(yaml_string)
|
||||
|
||||
# keeps convention of convert.py
|
||||
convert_yaml_import = yaml_import
|
||||
@@ -1,164 +0,0 @@
|
||||
import yaml
|
||||
import logging
|
||||
|
||||
|
||||
class YamlTag(object):
|
||||
"""
|
||||
Superclass for constructors of custom tags defined in yaml file.
|
||||
__str__ is overridden in subclass and used for serialization in module recorder.
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
def __getitem__(self, key):
|
||||
return getattr(self, key)
|
||||
def __getattr__(self, attr):
|
||||
return None
|
||||
def __repr__(self):
|
||||
return "<%s %s>" % (self.__class__.__name__, sorted(self.__dict__.items()))
|
||||
|
||||
class Assert(YamlTag):
|
||||
def __init__(self, model, id=None, severity=logging.WARNING, string="NONAME", **kwargs):
|
||||
self.model = model
|
||||
self.id = id
|
||||
self.severity = severity
|
||||
self.string = string
|
||||
super(Assert, self).__init__(**kwargs)
|
||||
|
||||
class Record(YamlTag):
|
||||
def __init__(self, model, id, use='id', view=True, **kwargs):
|
||||
self.model = model
|
||||
self.id = id
|
||||
self.view = view
|
||||
super(Record, self).__init__(**kwargs)
|
||||
def __str__(self):
|
||||
return '!record {model: %s, id: %s}:' % (str(self.model,), str(self.id,))
|
||||
|
||||
class Python(YamlTag):
|
||||
def __init__(self, model, severity=logging.ERROR, name="", **kwargs):
|
||||
self.model= model
|
||||
self.severity = severity
|
||||
self.name = name
|
||||
super(Python, self).__init__(**kwargs)
|
||||
def __str__(self):
|
||||
return '!python {model: %s}: |' % (str(self.model), )
|
||||
|
||||
class Menuitem(YamlTag):
|
||||
def __init__(self, id, name, **kwargs):
|
||||
self.id = id
|
||||
self.name = name
|
||||
super(Menuitem, self).__init__(**kwargs)
|
||||
|
||||
class ActWindow(YamlTag):
|
||||
def __init__(self, **kwargs):
|
||||
super(ActWindow, self).__init__(**kwargs)
|
||||
|
||||
class Function(YamlTag):
|
||||
def __init__(self, model, name, **kwargs):
|
||||
self.model = model
|
||||
self.name = name
|
||||
super(Function, self).__init__(**kwargs)
|
||||
|
||||
class Report(YamlTag):
|
||||
def __init__(self, model, name, string, **kwargs):
|
||||
self.model = model
|
||||
self.name = name
|
||||
self.string = string
|
||||
super(Report, self).__init__(**kwargs)
|
||||
|
||||
class Delete(YamlTag):
|
||||
def __init__(self, **kwargs):
|
||||
super(Delete, self).__init__(**kwargs)
|
||||
|
||||
class Context(YamlTag):
|
||||
def __init__(self, **kwargs):
|
||||
super(Context, self).__init__(**kwargs)
|
||||
|
||||
class Url(YamlTag):
|
||||
def __init__(self, **kwargs):
|
||||
super(Url, self).__init__(**kwargs)
|
||||
|
||||
class Eval(YamlTag):
|
||||
def __init__(self, expression):
|
||||
self.expression = expression
|
||||
super(Eval, self).__init__()
|
||||
def __str__(self):
|
||||
return '!eval %s' % str(self.expression)
|
||||
|
||||
class Ref(YamlTag):
|
||||
def __init__(self, expr="False", *args, **kwargs):
|
||||
self.expr = expr
|
||||
super(Ref, self).__init__(*args, **kwargs)
|
||||
def __str__(self):
|
||||
return 'ref(%s)' % repr(self.expr)
|
||||
|
||||
def assert_constructor(loader, node):
|
||||
kwargs = loader.construct_mapping(node)
|
||||
return Assert(**kwargs)
|
||||
|
||||
def record_constructor(loader, node):
|
||||
kwargs = loader.construct_mapping(node)
|
||||
assert "model" in kwargs, "'model' argument is required for !record"
|
||||
assert "id" in kwargs, "'id' argument is required for !record"
|
||||
return Record(**kwargs)
|
||||
|
||||
def python_constructor(loader, node):
|
||||
kwargs = loader.construct_mapping(node)
|
||||
kwargs['first_line'] = node.start_mark.line + 1
|
||||
return Python(**kwargs)
|
||||
|
||||
def menuitem_constructor(loader, node):
|
||||
kwargs = loader.construct_mapping(node)
|
||||
return Menuitem(**kwargs)
|
||||
|
||||
def act_window_constructor(loader, node):
|
||||
kwargs = loader.construct_mapping(node)
|
||||
return ActWindow(**kwargs)
|
||||
|
||||
def function_constructor(loader, node):
|
||||
kwargs = loader.construct_mapping(node)
|
||||
return Function(**kwargs)
|
||||
|
||||
def report_constructor(loader, node):
|
||||
kwargs = loader.construct_mapping(node)
|
||||
return Report(**kwargs)
|
||||
|
||||
def delete_constructor(loader, node):
|
||||
kwargs = loader.construct_mapping(node)
|
||||
return Delete(**kwargs)
|
||||
|
||||
def context_constructor(loader, node):
|
||||
kwargs = loader.construct_mapping(node)
|
||||
return Context(**kwargs)
|
||||
|
||||
def url_constructor(loader, node):
|
||||
kwargs = loader.construct_mapping(node)
|
||||
return Url(**kwargs)
|
||||
|
||||
def eval_constructor(loader, node):
|
||||
expression = loader.construct_scalar(node)
|
||||
return Eval(expression)
|
||||
|
||||
def ref_constructor(loader, tag_suffix, node):
|
||||
if tag_suffix == "id":
|
||||
kwargs = {"id": loader.construct_scalar(node)}
|
||||
else:
|
||||
kwargs = loader.construct_mapping(node)
|
||||
return Ref(**kwargs)
|
||||
|
||||
# Registers constructors for custom tags.
|
||||
# Constructors are actually defined globally: do not redefined them in another
|
||||
# class/file/package. This means that module recorder need import this file.
|
||||
def add_constructors():
|
||||
yaml.add_constructor(u"!assert", assert_constructor)
|
||||
yaml.add_constructor(u"!record", record_constructor)
|
||||
yaml.add_constructor(u"!python", python_constructor)
|
||||
yaml.add_constructor(u"!menuitem", menuitem_constructor)
|
||||
yaml.add_constructor(u"!act_window", act_window_constructor)
|
||||
yaml.add_constructor(u"!function", function_constructor)
|
||||
yaml.add_constructor(u"!report", report_constructor)
|
||||
yaml.add_constructor(u"!context", context_constructor)
|
||||
yaml.add_constructor(u"!delete", delete_constructor)
|
||||
yaml.add_constructor(u"!url", url_constructor)
|
||||
yaml.add_constructor(u"!eval", eval_constructor)
|
||||
yaml.add_multi_constructor(u"!ref", ref_constructor)
|
||||
add_constructors()
|
||||
@@ -108,7 +108,6 @@ def py2exe_options():
|
||||
'xlsxwriter',
|
||||
'xlwt',
|
||||
'xml', 'xml.dom',
|
||||
'yaml',
|
||||
],
|
||||
'excludes': ['Tkconstants', 'Tkinter', 'tcl'],
|
||||
}
|
||||
@@ -157,7 +156,6 @@ setup(
|
||||
'python-dateutil',
|
||||
'pytz',
|
||||
'pyusb >= 1.0.0b1',
|
||||
'pyyaml',
|
||||
'qrcode',
|
||||
'reportlab', # windows binary pypi.python.org/pypi/reportlab
|
||||
'requests',
|
||||
|
||||
Reference in New Issue
Block a user