[MERGE] Forward-port 9.0 up to 74d8cbc190

This commit is contained in:
Olivier Dony
2016-04-21 18:05:14 +02:00
45 changed files with 465 additions and 84 deletions
+1 -1
View File
@@ -72,7 +72,7 @@
<field name="type">other</field>
</record>
<record model="account.account.type" id="data_account_type_direct_costs">
<field name="name">Direct Costs</field>
<field name="name">Cost of Revenue</field>
<field name="type">other</field>
</record>
</data>
+11
View File
@@ -2571,6 +2571,12 @@ msgstr ""
msgid "Control-Access"
msgstr ""
#. module: account
#: model:account.account.type,name:account.data_account_type_direct_costs
#: model:ir.ui.view,arch_db:account.view_account_form
msgid "Cost of Revenue"
msgstr ""
#. module: account
#: model:ir.model.fields,field_description:account.field_account_move_line_counterpart
msgid "Counterpart"
@@ -5016,6 +5022,11 @@ msgstr ""
msgid "Less Cost of Sales"
msgstr ""
#. module: account
#: model:ir.ui.view,arch_db:account.view_account_form
msgid "Less Cost of Revenue"
msgstr ""
#. module: account
#: model:ir.ui.view,arch_db:account.view_account_form
msgid "Less Current Liabilities"
@@ -281,13 +281,15 @@ class account_journal(models.Model):
action_name = 'action_move_journal_line'
_journal_invoice_type_map = {
'sale': 'out_invoice',
'purchase': 'in_invoice',
'bank': 'bank',
'cash': 'cash',
'general': 'general',
('sale', None): 'out_invoice',
('purchase', None): 'in_invoice',
('sale', 'refund'): 'out_refund',
('purchase', 'refund'): 'in_refund',
('bank', None): 'bank',
('cash', None): 'cash',
('general', None): 'general',
}
invoice_type = _journal_invoice_type_map[self.type]
invoice_type = _journal_invoice_type_map[(self.type, self._context.get('invoice_type'))]
ctx = self._context.copy()
ctx.update({
+3 -2
View File
@@ -986,8 +986,9 @@ class AccountMoveLine(models.Model):
# Since create() receives ids instead of recordset, let's just use the old-api bridge
taxes = self.env['account.tax'].browse(tax_ids)
currency = self.env['res.currency'].browse(vals.get('currency_id'))
partner = self.env['res.partner'].browse(vals.get('partner_id'))
res = taxes.compute_all(amount,
currency, 1, vals.get('product_id'), vals.get('partner_id'))
currency, 1, vals.get('product_id'), partner)
# Adjust line amount if any tax is price_include
if abs(res['total_excluded']) < abs(amount):
if vals['debit'] != 0.0: vals['debit'] = res['total_excluded']
@@ -1147,7 +1148,7 @@ class AccountMoveLine(models.Model):
@api.model
def _query_get(self, domain=None):
context = dict(self._context or {})
domain = domain and safe_eval(domain) or []
domain = domain and safe_eval(str(domain)) or []
date_field = 'date'
if context.get('aged_balance'):
@@ -117,7 +117,7 @@
</a>
</div>
<div>
<a type="object" name="open_action" context="{'use_domain': [('type','in',('out_refund','in_refund'))]}">
<a type="object" name="open_action" context="{'use_domain': [('type','in',('out_refund','in_refund'))], 'invoice_type': 'refund'}">
<span t-if="journal_type == 'sale'">Refunds</span>
<span t-if="journal_type == 'purchase'">Refunds</span>
</a>
+1 -2
View File
@@ -36,8 +36,7 @@
<div class="oe_account_help">
<p class="oe_account_font_help text-center">Profit &amp; Loss</p>
<p class="oe_account_font_help oe_account_font_title">Income</p>
<p class="oe_account_font_help oe_account_font_title">Less Cost of Sales</p>
<p class="oe_account_font_help oe_account_font_content">Direct Costs</p>
<p class="oe_account_font_help oe_account_font_title">Less Cost of Revenue</p>
<p class="oe_account_font_help" style="color: grey;border-top: 2px solid;border-bottom: 2px solid;">GROSS PROFIT</p>
<p class="oe_account_font_help oe_account_font_title">Plus Other Income</p>
<p class="oe_account_font_help oe_account_font_title">Less Expenses</p>
@@ -3,6 +3,8 @@
from datetime import datetime
from dateutil.relativedelta import relativedelta
from openerp import api, fields, models
from openerp.exceptions import Warning
from openerp.tools.translate import _
import openerp.addons.decimal_precision as dp
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
@@ -14,6 +16,9 @@ class AccountInvoice(models.Model):
def action_move_create(self):
result = super(AccountInvoice, self).action_move_create()
for inv in self:
if inv.number:
if self.env['account.asset.asset'].sudo().search([('code', '=', inv.number)]):
raise Warning(_('You already have assets with the reference %s.\nPlease delete these assets before creating new ones for this invoice.') % (inv.number,))
inv.invoice_line_ids.asset_create()
return result
@@ -85,3 +90,13 @@ class ProductTemplate(models.Model):
_inherit = 'product.template'
asset_category_id = fields.Many2one('account.asset.category', string='Asset Type', ondelete="restrict")
deferred_revenue_category_id = fields.Many2one('account.asset.category', string='Deferred Revenue Type', ondelete="restrict")
@api.onchange('deferred_revenue_category_id')
def onchange_deferred_revenue(self):
if self.deferred_revenue_category_id:
self.property_account_income_id = self.deferred_revenue_category_id.account_asset_id
@api.onchange('asset_category_id')
def onchange_asset(self):
if self.asset_category_id:
self.property_account_expense_id = self.asset_category_id.account_asset_id
@@ -1023,6 +1023,12 @@ msgstr ""
msgid "Year"
msgstr ""
#. module: account_asset
#: code:addons/account_asset/account_asset_invoice.py:53
#, python-format
msgid "You already have assets with the reference %s.\nPlease delete these assets before creating new ones for this invoice."
msgstr ""
#. module: account_asset
#: code:addons/account_asset/account_asset.py:100
#, python-format
+3 -1
View File
@@ -961,7 +961,9 @@ class calendar_event(osv.Model):
def _check_closing_date(self, cr, uid, ids, context=None):
for event in self.browse(cr, uid, ids, context=context):
if event.stop < event.start:
if event.start_datetime and event.stop_datetime < event.start_datetime:
return False
if event.start_date and event.stop_date < event.start_date:
return False
return True
+3 -3
View File
@@ -216,9 +216,9 @@ class HrExpense(models.Model):
for expense in self:
if expense.date > maxdate:
maxdate = expense.date
if expense.journal_id not in journal_dict:
journal_dict[expense.journal_id] = []
journal_dict[expense.journal_id].append(expense)
jrn = expense.bank_journal_id if expense.payment_mode == 'company_account' else expense.journal_id
journal_dict.setdefault(jrn, [])
journal_dict[jrn].append(expense)
for journal, expense_list in journal_dict.items():
#create the move that will contain the accounting entries
+1 -1
View File
@@ -445,7 +445,7 @@ class Escpos:
img_rgba = Image.open(f)
img = Image.new('RGB', img_rgba.size, (255,255,255))
channels = img_rgba.split()
if len(channels) > 1:
if len(channels) > 3:
# use alpha channel as mask
img.paste(img_rgba, mask=channels[3])
else:
+10
View File
@@ -1,6 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="account_fr_tag_salaires" model="account.account.tag">
<field name="name">Salaires</field>
<field name="applicability">accounts</field>
</record>
<record id="account_fr_tag_charges_sociales" model="account.account.tag">
<field name="name">Charges Sociales</field>
<field name="applicability">accounts</field>
</record>
<menuitem id="account_reports_fr_statements_menu" name="French Statements" parent="account.menu_finance_reports" sequence="3" groups="account.group_account_user"/>
<!--
+6 -3
View File
@@ -125,11 +125,11 @@ class AccountFrFec(models.TransientModel):
aa.code AS CompteNum,
replace(aa.name, '|', '/') AS CompteLib,
CASE WHEN rp.ref IS null OR rp.ref = ''
THEN 'ID ' || rp.id
THEN COALESCE('ID ' || rp.id, '')
ELSE rp.ref
END
AS CompAuxNum,
replace(rp.name, '|', '/') AS CompAuxLib,
COALESCE(replace(rp.name, '|', '/'), '') AS CompAuxLib,
CASE WHEN am.ref IS null OR am.ref = ''
THEN '-'
ELSE replace(am.ref, '|', '/')
@@ -142,7 +142,10 @@ class AccountFrFec(models.TransientModel):
CASE WHEN rec.name IS NULL THEN '' ELSE rec.name END AS EcritureLet,
CASE WHEN aml.full_reconcile_id IS NULL THEN '' ELSE TO_CHAR(rec.create_date, 'YYYYMMDD') END AS DateLet,
TO_CHAR(am.date, 'YYYYMMDD') AS ValidDate,
replace(CASE WHEN aml.amount_currency = 0 THEN '' ELSE to_char(aml.amount_currency, '999999999999999D99') END, '.', ',') AS Montantdevise,
CASE
WHEN aml.amount_currency IS NULL OR aml.amount_currency = 0 THEN ''
ELSE replace(to_char(aml.amount_currency, '999999999999999D99'), '.', ',')
END AS Montantdevise,
CASE WHEN aml.currency_id IS NULL THEN '' ELSE rc.name END AS Idevise
FROM
account_move_line aml
@@ -156,8 +156,9 @@
<record id="conf_cog" model="account.account.template">
<field name="code">210</field>
<field name="name">Cost of Goods Sold</field>
<field name="user_type_id" ref="account.data_account_type_expenses"/>
<field name="name">Cost of Revenue</field>
<!-- FORWARDPORT UP TO SAAS-10 -->
<field name="user_type_id" ref="account.data_account_type_direct_costs"/>
<field name="chart_template_id" ref="configurable_chart_template"/>
<field name="tag_ids" eval="[(6,0,[ref('account.account_tag_operating')])]"/>
</record>
+2 -2
View File
@@ -192,8 +192,8 @@
<record id="a_exp_cogs" model="account.account.template">
<field name="code">5100</field>
<field name="name">Cost of goods sold</field>
<field name="user_type_id" ref="account.data_account_type_expenses"/>
<field name="name">Cost of Revenue</field>
<field name="user_type_id" ref="account.data_account_type_direct_costs"/>
<field name="chart_template_id" ref="chart"/>
</record>
+2 -2
View File
@@ -762,7 +762,7 @@ class Message(models.Model):
# TDE: probably clean me
parent_ids = [message.get('parent_id') for mid, message in message_values.iteritems()
if message.get('parent_id')]
self._cr.execute("""SELECT DISTINCT m.id FROM "%s" m
self._cr.execute("""SELECT DISTINCT m.id, partner_rel.res_partner_id, channel_partner.partner_id FROM "%s" m
LEFT JOIN "mail_message_res_partner_rel" partner_rel
ON partner_rel.mail_message_id = m.id AND partner_rel.res_partner_id = (%%s)
LEFT JOIN "mail_message_mail_channel_rel" channel_rel
@@ -772,7 +772,7 @@ class Message(models.Model):
LEFT JOIN "mail_channel_partner" channel_partner
ON channel_partner.channel_id = channel.id AND channel_partner.partner_id = (%%s)
WHERE m.id = ANY (%%s)""" % self._table, (self.env.user.partner_id.id, self.env.user.partner_id.id, parent_ids,))
not_parent_ids = [mid[0] for mid in self._cr.fetchall()]
not_parent_ids = [mid[0] for mid in self._cr.fetchall() if any([mid[1], mid[2]])]
notified_ids += [mid for mid, message in message_values.iteritems()
if message.get('parent_id') in not_parent_ids]
+8 -3
View File
@@ -801,15 +801,20 @@ class MailThread(models.AbstractModel):
record_set = None
def _create_bounce_email():
self.env['mail.mail'].create({
bounce_to = decode_header(message, 'Return-Path') or email_from
bounce_mail_values = {
'body_html': '<div><p>Hello,</p>'
'<p>The following email sent to %s cannot be accepted because this is '
'a private email address. Only allowed people can contact us at this address.</p></div>'
'<blockquote>%s</blockquote>' % (message.get('to'), message_dict.get('body')),
'subject': 'Re: %s' % message.get('subject'),
'email_to': message.get('from'),
'email_to': bounce_to,
'auto_delete': True,
}).send()
}
bounce_from = self.env['ir.mail_server']._get_default_bounce_address()
if bounce_from:
bounce_mail_values['email_from'] = 'MAILER-DAEMON <%s>' % bounce_from
self.env['mail.mail'].create(bounce_mail_values).send()
def _warn(message):
_logger.info('Routing mail with Message-Id %s: route %s: %s',
+1 -1
View File
@@ -919,7 +919,7 @@ var Chatter = form_common.AbstractField.extend({
this.mute_new_message_button(true);
},
close_composer: function (force) {
if (this.composer.is_empty() || force) {
if (this.composer && (this.composer.is_empty() || force)) {
this.composer.do_hide();
this.composer.$input.val('');
this.mute_new_message_button(false);
+2 -2
View File
@@ -313,8 +313,8 @@ class TestMailgateway(TestMail):
'message_process: incoming email on Partners alias should send a bounce email')
self.assertIn('New Frogs', self._mails[0].get('subject'),
'message_process: bounce email on Partners alias should contain the original subject')
self.assertIn(formataddr(('Sylvie Lelitre', 'test.sylvie.lelitre@agrolait.com')), self._mails[0].get('email_to'),
'message_process: bounce email on Partners alias should have original email sender as recipient')
self.assertIn('whatever-2a840@postmaster.twitter.com', self._mails[0].get('email_to'),
'message_process: bounce email on Partners alias should go to Return-Path address')
@mute_logger('openerp.addons.mail.models.mail_thread', 'openerp.models', 'openerp.addons.mail.models.mail_mail')
def test_message_process_alias_followers_bounce(self):
+1
View File
@@ -1124,6 +1124,7 @@ class mrp_production(osv.osv):
data = {
'name': production.name,
'date': production.date_planned,
'date_expected': production.date_planned,
'product_id': production.product_id.id,
'product_uom': production.product_uom.id,
'product_uom_qty': production.product_qty,
+1 -1
View File
@@ -1070,7 +1070,7 @@
</t>
<t t-if="product.to_weight">
<span class="price-tag">
<t t-esc="widget.format_currency(product.price,'Product Price')+'/Kg'"/>
<t t-esc="widget.format_currency(product.price,'Product Price')+'/'+widget.pos.units_by_id[product.uom_id[0]].name"/>
</span>
</t>
</div>
+7
View File
@@ -600,6 +600,13 @@ class SaleOrderLine(models.Model):
@api.model
def create(self, values):
onchange_fields = ['name', 'price_unit', 'product_uom', 'tax_id']
if values.get('order_id') and values.get('product_id') and any(f not in values for f in onchange_fields):
line = self.new(values)
line.product_id_change()
for field in onchange_fields:
if field not in values:
values[field] = line._fields[field].convert_to_write(line[field])
line = super(SaleOrderLine, self).create(values)
if line.state == 'sale':
if (not line.order_id.project_id and
+1 -1
View File
@@ -61,7 +61,7 @@ class stock_location(osv.osv):
res[m.id] = m.name
parent = m.location_id
while parent:
res[m.id] = parent.name + ' / ' + res[m.id]
res[m.id] = parent.name + '/' + res[m.id]
parent = parent.location_id
return res
+25
View File
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import fields, osv
from openerp.tools import float_compare, float_round
from openerp.tools.translate import _
from openerp import SUPERUSER_ID, api, models
from openerp.exceptions import UserError
@@ -214,8 +215,32 @@ class stock_quant(osv.osv):
self._create_account_move_line(cr, uid, quants, move, acc_valuation, acc_dest, journal_id, context=ctx)
def _quant_create(self, cr, uid, qty, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, force_location_from=False, force_location_to=False, context=None):
quant_obj = self.pool.get('stock.quant')
quant = super(stock_quant, self)._quant_create(cr, uid, qty, move, lot_id=lot_id, owner_id=owner_id, src_package_id=src_package_id, dest_package_id=dest_package_id, force_location_from=force_location_from, force_location_to=force_location_to, context=context)
self._account_entry_move(cr, uid, [quant], move, context)
if move.product_id.valuation == 'real_time':
# If the precision required for the variable quant cost is larger than the accounting
# precision, inconsistencies between the stock valuation and the accounting entries
# may arise.
# For example, a box of 13 units is bought 15.00. If the products leave the
# stock one unit at a time, the amount related to the cost will correspond to
# round(15/13, 2)*13 = 14.95. To avoid this case, we split the quant in 12 + 1, then
# record the difference on the new quant.
# We need to make sure to able to extract at least one unit of the product. There is
# an arbitrary minimum quantity set to 2.0 from which we consider we can extract a
# unit and adapt the cost.
curr_rounding = move.company_id.currency_id.rounding
cost_rounded = float_round(quant.cost, precision_rounding=curr_rounding)
cost_correct = cost_rounded
if float_compare(quant.product_id.uom_id.rounding, 1.0, precision_digits=1) == 0\
and float_compare(quant.qty * quant.cost, quant.qty * cost_rounded, precision_rounding=curr_rounding) != 0\
and float_compare(quant.qty, 2.0, precision_rounding=quant.product_id.uom_id.rounding) >= 0:
qty = quant.qty
cost = quant.cost
quant_correct = quant_obj._quant_split(cr, uid, quant, quant.qty - 1.0, context=context)
cost_correct += (qty * cost) - (qty * cost_rounded)
quant_obj.write(cr, SUPERUSER_ID, [quant.id], {'cost': cost_rounded}, context=context)
quant_obj.write(cr, SUPERUSER_ID, [quant_correct.id], {'cost': cost_correct}, context=context)
return quant
def move_quants_write(self, cr, uid, quants, move, location_dest_id, dest_package_id, lot_id=False, entire_pack=False, context=None):
+1
View File
@@ -27,6 +27,7 @@ This module allows you to easily add extra costs on pickings and decide the spli
'../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,
@@ -209,13 +209,41 @@ class stock_landed_cost(osv.osv):
continue
per_unit = line.final_cost / line.quantity
diff = per_unit - line.former_cost_per_unit
quants = [quant for quant in line.move_id.quant_ids]
# If the precision required for the variable diff is larger than the accounting
# precision, inconsistencies between the stock valuation and the accounting entries
# may arise.
# For example, a landed cost of 15 divided in 13 units. If the products leave the
# stock one unit at a time, the amount related to the landed cost will correspond to
# round(15/13, 2)*13 = 14.95. To avoid this case, we split the quant in 12 + 1, then
# record the difference on the new quant.
# We need to make sure to able to extract at least one unit of the product. There is
# an arbitrary minimum quantity set to 2.0 from which we consider we can extract a
# unit and adapt the cost.
curr_rounding = line.move_id.company_id.currency_id.rounding
diff_rounded = float_round(diff, precision_rounding=curr_rounding)
diff_correct = diff_rounded
quants = line.move_id.quant_ids.sorted(key=lambda r: r.qty, reverse=True)
quant_correct = False
if quants\
and float_compare(quants[0].product_id.uom_id.rounding, 1.0, precision_digits=1) == 0\
and float_compare(line.quantity * diff, line.quantity * diff_rounded, precision_rounding=curr_rounding) != 0\
and float_compare(quants[0].qty, 2.0, precision_rounding=quants[0].product_id.uom_id.rounding) >= 0:
# Search for existing quant of quantity = 1.0 to avoid creating a new one
quant_correct = quants.filtered(lambda r: float_compare(r.qty, 1.0, precision_rounding=quants[0].product_id.uom_id.rounding) == 0)
if not quant_correct:
quant_correct = quant_obj._quant_split(cr, uid, quants[0], quants[0].qty - 1.0, context=context)
else:
quant_correct = quant_correct[0]
quants = quants - quant_correct
diff_correct += (line.quantity * diff) - (line.quantity * diff_rounded)
diff = diff_rounded
quant_dict = {}
for quant in quants:
if quant.id not in quant_dict:
quant_dict[quant.id] = quant.cost + diff
else:
quant_dict[quant.id] += diff
quant_dict[quant.id] = quant.cost + diff
if quant_correct:
quant_dict[quant_correct.id] = quant_correct.cost + diff_correct
for key, value in quant_dict.items():
quant_obj.write(cr, SUPERUSER_ID, key, {'cost': value}, context=context)
qty_out = 0
@@ -0,0 +1,175 @@
-
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: real
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: real
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}: |
pick = self.browse(cr, uid, [ref("picking_landed_cost_3")], context=context)
pick.move_lines.price_unit = 1.0
self.action_confirm(cr, uid, [ref("picking_landed_cost_3")], context=context)
self.action_assign(cr, uid, [ref("picking_landed_cost_3")], context=context)
self.action_done(cr, uid, [ref("picking_landed_cost_3")], context=context)
quants = pick.move_lines.quant_ids
assert len(quants) == 1
assert quants.qty == 13
assert quants.cost == 1.0
-
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}: |
self.compute_landed_cost(cr, uid, [ref("stock_landed_cost_2")])
-
I check the valuation adjustment lines
-
!python {model: stock.landed.cost}: |
landed_cost = self.browse(cr, uid, ref("stock_landed_cost_2"))
for valuation in landed_cost.valuation_adjustment_lines:
assert valuation.additional_landed_cost == 15
-
I confirm the landed cost
-
!python {model: stock.landed.cost}: |
self.button_validate(cr, uid, [ref("stock_landed_cost_2")])
-
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
-
I check the quants quantity and cost
-
!python {model: stock.landed.cost}: |
landed_cost = self.browse(cr, uid, ref("stock_landed_cost_2"))
for valuation in landed_cost.valuation_adjustment_lines:
quants = valuation.move_id.quant_ids
assert quants.mapped('qty') == [12.0, 1.0]
assert quants.mapped('cost') == [2.15, 2.2]
-
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}: |
pick = self.browse(cr, uid, [ref("picking_landed_cost_4")], context=context)
pick.move_lines.price_unit = 17.0/12.0
self.action_confirm(cr, uid, [ref("picking_landed_cost_4")], context=context)
self.action_assign(cr, uid, [ref("picking_landed_cost_4")], context=context)
self.action_done(cr, uid, [ref("picking_landed_cost_4")], context=context)
quants = pick.move_lines.quant_ids
assert len(quants) == 2
assert quants.mapped('qty') == [11.0, 1.0]
assert [round(c, 2) for c in quants.mapped('cost')] == [1.42, 1.38]
-
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}: |
self.compute_landed_cost(cr, uid, [ref("stock_landed_cost_3")])
-
I check the valuation adjustment lines
-
!python {model: stock.landed.cost}: |
landed_cost = self.browse(cr, uid, ref("stock_landed_cost_3"))
for valuation in landed_cost.valuation_adjustment_lines:
assert valuation.additional_landed_cost == 11
-
I confirm the landed cost
-
!python {model: stock.landed.cost}: |
self.button_validate(cr, uid, [ref("stock_landed_cost_3")])
-
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
-
I check the quants quantity and cost
-
!python {model: stock.landed.cost}: |
landed_cost = self.browse(cr, uid, ref("stock_landed_cost_3"))
for valuation in landed_cost.valuation_adjustment_lines:
quants = valuation.move_id.quant_ids
assert quants.mapped('qty') == [11.0, 1.0]
assert [round(c, 2) for c in quants.mapped('cost')] == [2.34, 2.26]
+3 -1
View File
@@ -203,7 +203,9 @@ function get_file(options) {
// opening a new window seems the best way to workaround
if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) {
var params = _.extend({}, options.data || {}, {token: token});
var url = this.url(options.url, params);
var url = options.session.url(options.url, params);
if (options.complete) { options.complete(); }
return window.open(url);
}
+4 -2
View File
@@ -320,6 +320,7 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
this.index = null;
this._sort = [];
this._model = new Model(model, context);
this.orderer = new utils.DropMisordered();
},
previous: function () {
this.index -= 1;
@@ -408,10 +409,11 @@ var DataSet = Class.extend(mixins.PropertiesMixin, {
read_slice: function (fields, options) {
var self = this;
options = options || {};
return this._model.query(fields)
var query = this._model.query(fields)
.limit(options.limit || false)
.offset(options.offset || 0)
.all().done(function (records) {
.all();
return this.orderer.add(query).done(function (records) {
self.ids = _(records).pluck('id');
});
},
@@ -271,6 +271,7 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
if (this.override_session){
options.data.session_id = this.session_id;
}
options.session = this;
ajax.get_file(options);
},
synchronized_mode: function(to_execute) {
@@ -1901,6 +1901,7 @@ var Column = Class.extend({
row_data[this.id].value, this, options.value_if_empty));
}
});
ListView.Column = Column;
var MetaColumn = Column.extend({
meta: true,
@@ -1,7 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from lxml import etree
from openerp import models, api
from openerp.tools.translate import encode, xml_translate
def edit_translation_mapping(data):
data = dict(data, model=data['name'].partition(',')[0])
@@ -16,3 +19,20 @@ class ir_translation(models.Model):
self.insert_missing(field, records)
return edit_translation_mapping
return super(ir_translation, self)._get_terms_mapping(field, records)
@api.multi
def save_html(self, value):
""" Convert the HTML fragment ``value`` to XML if necessary, and write
it as the value of translation ``self``.
"""
assert len(self) == 1 and self.type == 'model'
mname, fname = self.name.split(',')
field = self.env[mname]._fields[fname]
if field.translate == xml_translate:
# wrap value inside a div and parse it as HTML
div = "<div>%s</div>" % encode(value)
root = etree.fromstring(div, etree.HTMLParser(encoding='utf-8'))
# root is html > body > div
# serialize div as XML and discard surrounding tags
value = etree.tostring(root[0][0], encoding='utf-8')[5:-6]
return self.write({'value': value})
@@ -47,10 +47,10 @@ var RTE_Translate = rte.Class.extend({
return ajax.jsonRpc('/web/dataset/call', 'call', {
model: 'ir.translation',
method: 'write',
method: 'save_html',
args: [
[+$el.data('oe-translation-id')],
{'value': translation_content, 'state': 'translated'},
translation_content,
context || base.get_context()
],
});
@@ -15,22 +15,6 @@ var X2ManyKanbanView = KanbanView.extend({
});
this._super($node, options);
},
load_records: function (offset, dataset) {
var options = {
'limit': this.limit,
'offset': offset,
};
dataset = dataset || this.dataset;
return dataset
.read_slice(this.fields_keys.concat(['__last_update']), options)
.then(function(records) {
return {
records: records,
is_empty: !records.length,
grouped: false,
};
});
},
});
var One2ManyKanbanView = X2ManyKanbanView.extend({
@@ -102,3 +86,4 @@ core.view_registry.add('one2many_kanban', One2ManyKanbanView);
core.view_registry.add('many2many_kanban', Many2ManyKanbanView);
});
+15 -20
View File
@@ -130,17 +130,13 @@ var KanbanView = View.extend({
return this.search_orderer
.add(options.grouped ? this.load_groups(options) : this.load_records())
.then(function (data) {
var new_ids;
_.extend(self, options);
if (options.grouped) {
new_ids = _.union.apply(null, _.map(data.groups, function (group) {
var new_ids = _.union.apply(null, _.map(data.groups, function (group) {
return group.dataset.ids;
}));
} else {
new_ids = _.pluck(data.records, 'id');
self.dataset._length = data.count;
self.dataset.alter_ids(new_ids);
}
self.dataset.alter_ids(new_ids);
self.data = data;
})
.then(this.proxy('render'))
@@ -157,20 +153,19 @@ var KanbanView = View.extend({
},
load_records: function (offset, dataset) {
dataset = dataset || this.dataset;
var query = dataset._model.query(this.fields_keys.concat(['__last_update']))
.limit(this.limit || false)
.offset(offset || 0);
return query.all()
.then(function (records) {
return query.count().then(function(count) {
return {
records: records,
is_empty: !records.length,
grouped: false,
count: count,
};
})
var options = {
'limit': this.limit,
'offset': offset,
};
dataset = dataset || this.dataset;
return dataset
.read_slice(this.fields_keys.concat(['__last_update']), options)
.then(function(records) {
return {
records: records,
is_empty: !records.length,
grouped: false,
};
});
},
@@ -384,7 +384,7 @@
font-size: 18px;
font-weight: 300;
.o-flex(1, 1, auto);
white-space: nowrap;
.o-text-overflow();
padding-top: 5px;
}
.o_kanban_quick_add i {
+16 -1
View File
@@ -2,9 +2,12 @@
import base64
import json
import pytz
from datetime import datetime
from psycopg2 import IntegrityError
from openerp import http, SUPERUSER_ID
from openerp.http import request
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools.translate import _
from openerp.exceptions import ValidationError
from openerp.addons.base.ir.ir_qweb import nl2br
@@ -60,6 +63,17 @@ class WebsiteForm(http.Controller):
def boolean(self, field_label, field_input):
return bool(field_input)
def date(self, field_label, field_input):
lang = request.env['ir.qweb.field'].user_lang()
return datetime.strptime(field_input, lang.date_format).strftime(DEFAULT_SERVER_DATE_FORMAT)
def datetime(self, field_label, field_input):
lang = request.env['ir.qweb.field'].user_lang()
strftime_format = (u"%s %s" % (lang.date_format, lang.time_format))
user_tz = pytz.timezone(request.context.get('tz') or request.env.user.tz or 'UTC')
dt = user_tz.localize(datetime.strptime(field_input, strftime_format)).astimezone(pytz.utc)
return dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
def binary(self, field_label, field_input):
return base64.b64encode(field_input.read())
@@ -73,7 +87,8 @@ class WebsiteForm(http.Controller):
'char': identity,
'text': identity,
'html': identity,
'datetime': identity,
'date': date,
'datetime': datetime,
'many2one': integer,
'one2many': one2many,
'many2many':many2many,
+18
View File
@@ -0,0 +1,18 @@
Belgium 2016-04-11
Van Roey agrees to the terms of the Odoo Corporate
Contributor License Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Yenthe Van Ginneken yenthe.vanginneken@vanroey.be https://github.com/Yenthe666
List of contributors:
Yenthe Van Ginneken yenthe.vanginneken@vanroey.be https://github.com/Yenthe666
Maarten Wijnants maarten.wijnants@vanroey.be https://github.com/weilant
Vincent Adriaensen vincent.adriaensen@vanroey.be https://github.com/vincentadriaensen
Pieter Paulussen pieter.paulussen@vanroey.be https://github.com/PieterPaulussen
+16
View File
@@ -0,0 +1,16 @@
Spain, March 11, 2016
bisneSmart S.C. agrees to the terms of the Odoo Corporate Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
bisneSmart S.C. hola@bisnesmart.com https://github.com/bisnesmart
List of contributors:
Gonzalo Borrás Beta gborras@bisnesmart.com https://github.com/gonxi
Rubén Cabrera Martínez rcabrera@bisnesmart.com https://github.com/rubencabrera
+1
View File
@@ -27,4 +27,5 @@ Romain Deheele romain.deheele@camptocamp.com https://github.com/rdeheele
Maxime Wiot maxime.wiot@camptocamp.com https://github.com/maxime-c2c
Ferdinand Gassauer ferdinand.gassauer@camptocamp.com https://github.com/ferdiga
Jean-Baptiste Aubort jean-baptiste.aubort@camptocamp.com
Cyril Gaudin cyril.gaudin@camptocamp.com https://github.com/cyrilgdn
+9
View File
@@ -0,0 +1,9 @@
Spain, 2016-04-08
I hereby agree to the terms of the Odoo Individual Contributor License Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this declaration.
Signed,
Eduardo Rodriguez erocre@gmail.com https://github.com/erocre
+11
View File
@@ -0,0 +1,11 @@
Ecuador, 14th of March, 2016
I hereby agree to the terms of the Odoo Individual Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Javier Casas javcasas@gmail.com https://github.com/javcasas
+2 -1
View File
@@ -1,4 +1,4 @@
United States of America, 2015-10-22
United States of America, 2016-04-07
I hereby agree to the terms of the Odoo Individual Contributor License
Agreement v1.0.
@@ -9,3 +9,4 @@ declaration.
Signed,
Stephen Medina stephen@lilmail.xyz https://github.com/stephen144
Stephen Medina stephenm@lsm.org https://github.com/stephen144
+11
View File
@@ -0,0 +1,11 @@
United States, 2016-04-19
I hereby agree to the terms of the Odoo Individual Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Cody Kitterman ckitterm@gmail.com https://github.com/tesserb
+2 -1
View File
@@ -55,7 +55,8 @@ SLEEP_INTERVAL = 60 # 1 min
def memory_info(process):
""" psutil < 2.0 does not have memory_info, >= 3.0 does not have
get_memory_info """
return (getattr(process, 'memory_info', None) or process.get_memory_info)()
pmem = (getattr(process, 'memory_info', None) or process.get_memory_info)()
return (pmem.rss, pmem.vms)
#----------------------------------------------------------
# Werkzeug WSGI servers patched