[MERGE] forward port branch 10.0 up to ffd4442b06

This commit is contained in:
Christophe Simonis
2017-02-10 13:17:02 +01:00
49 changed files with 416 additions and 117 deletions
+5 -2
View File
@@ -486,7 +486,6 @@ class AccountInvoice(models.Model):
addr = self.partner_id.address_get(['delivery'])
fiscal_position = self.env['account.fiscal.position'].get_fiscal_position(self.partner_id.id, delivery_id=addr['delivery'])
bank_id = p.bank_ids and p.bank_ids.ids[0] or False
# If partner has no warning, check its company
if p.invoice_warn == 'no-message' and p.parent_id:
@@ -507,8 +506,12 @@ class AccountInvoice(models.Model):
self.payment_term_id = payment_term_id
self.fiscal_position_id = fiscal_position
if type in ('in_invoice', 'in_refund'):
if type in ('in_invoice', 'out_refund'):
bank_ids = p.commercial_partner_id.bank_ids
bank_id = bank_ids[0].id if bank_ids else False
self.partner_bank_id = bank_id
return {'domain': {'partner_bank_id': [('id', 'in', bank_ids.ids)]}}
return {}
@api.onchange('journal_id')
+20 -5
View File
@@ -709,22 +709,37 @@ class AccountMoveLine(models.Model):
line_currency = (line.currency_id and line.amount_currency) and line.currency_id or company_currency
amount_currency_str = ""
total_amount_currency_str = ""
if line_currency != company_currency and target_currency != company_currency:
if line_currency != company_currency and target_currency == line_currency:
# The payment currency is the invoice currency, but they are different than the company currency
# We use the `amount_currency` computed during the invoice validation, at the invoice date
# to avoid exchange gain/loss
# e.g. an invoice of 100€ must be paid with 100€, whatever the company currency and the exchange rates
total_amount = line.amount_currency
actual_debit = debit > 0 and amount_currency or 0.0
actual_credit = credit > 0 and -amount_currency or 0.0
currency = line_currency
else:
# Either:
# - the invoice, payment, company currencies are all the same,
# - the payment currency is the company currency, but the invoice currency is different,
# - the invoice currency is the company currency, but the payment currency is different,
# - the invoice, payment and company currencies are all different.
# For the two first cases, we can simply use the debit/credit of the invoice move line, which are always in the company currency,
# and this is what the target need.
# For the two last cases, we can use the debit/credit which are in the company currency, and then change them to the target currency
total_amount = abs(debit - credit)
actual_debit = debit > 0 and amount or 0.0
actual_credit = credit > 0 and -amount or 0.0
if line_currency != target_currency and target_currency != company_currency:
currency = company_currency
if line_currency != target_currency:
amount_currency_str = formatLang(self.env, abs(actual_debit or actual_credit), currency_obj=line_currency)
total_amount_currency_str = formatLang(self.env, total_amount, currency_obj=line_currency)
if currency != target_currency:
ctx = context.copy()
ctx.update({'date': target_date or line.date})
total_amount = line_currency.with_context(ctx).compute(total_amount, target_currency)
actual_debit = line_currency.with_context(ctx).compute(actual_debit, target_currency)
actual_credit = line_currency.with_context(ctx).compute(actual_credit, target_currency)
total_amount = currency.with_context(ctx).compute(total_amount, target_currency)
actual_debit = currency.with_context(ctx).compute(actual_debit, target_currency)
actual_credit = currency.with_context(ctx).compute(actual_credit, target_currency)
amount_str = formatLang(self.env, abs(actual_debit or actual_credit), currency_obj=target_currency)
total_amount_str = formatLang(self.env, total_amount, currency_obj=target_currency)
+2 -2
View File
@@ -21,7 +21,7 @@ class account_payment_method(models.Model):
_name = "account.payment.method"
_description = "Payment Methods"
name = fields.Char(required=True)
name = fields.Char(required=True, translate=True)
code = fields.Char(required=True) # For internal identification
payment_type = fields.Selection([('inbound', 'Inbound'), ('outbound', 'Outbound')], required=True)
@@ -400,7 +400,7 @@ class account_payment(models.Model):
# the writeoff debit and credit must be computed from the invoice residual in company currency
# minus the payment amount in company currency, and not from the payment difference in the payment currency
# to avoid loss of precision during the currency rate computations. See revision 20935462a0cabeb45480ce70114ff2f4e91eaf79 for a detailed example.
total_residual_company_signed = self._compute_total_invoices_amount()
total_residual_company_signed = sum(invoice.residual_company_signed for invoice in self.invoice_ids)
total_payment_company_signed = self.currency_id.with_context(date=self.payment_date).compute(self.amount, self.company_id.currency_id)
if self.invoice_ids[0].type in ['in_invoice', 'out_refund']:
amount_wo = total_payment_company_signed - total_residual_company_signed
+1 -2
View File
@@ -1106,7 +1106,6 @@
<field name="move_id" required="False"/>
<newline/>
<field name="date_maturity"/>
<field name="date"/>
<field name="blocked"/>
<newline/>
<field name="tax_ids" readonly="1"/>
@@ -1195,7 +1194,7 @@
<filter string="Unposted" domain="[('move_id.state','=','draft')]" help="Unposted Journal Items"/>
<filter name="posted" string="Posted" domain="[('move_id.state','=','posted')]" help="Posted Journal Items"/>
<separator/>
<filter string="Unreconciled" domain="[('full_reconcile_id','=',False), ('account_id.reconcile','=',True)]" help="Journal items where matching number isn't set" name="unreconciled"/>
<filter string="Unreconciled" domain="[('full_reconcile_id', '=', False), ('reconciled','=', False), ('account_id.reconcile','=',True)]" help="Journal items where matching number isn't set" name="unreconciled"/>
<separator/>
<filter string="Payable" domain="[('account_id.internal_type', '=', 'payable')]" help="From Payable accounts" name="payable"/>
<filter string="Receivable" domain="[('account_id.internal_type', '=', 'receivable')]" help="From Receivable accounts" name="receivable"/>
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate='1'>
<function id="default_generate_sales_team_alias"
model="ir.values" name="set_default"
eval="('sale.config.settings', 'generate_sales_team_alias', True)"/>
</data>
</odoo>
+3
View File
@@ -583,6 +583,9 @@
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all opportunities for which the next action date is before today"/>
<filter string="Overdue Opportunities" name="overdue_opp"
domain="[('date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all opportunities with a deadline for which the next action date is before today"/>
<separator/>
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
<filter string="Lost" domain="[('active', '=', False)]"/>
@@ -16,6 +16,7 @@
border-bottom: 0px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
z-index: 5;
}
.o_chat_window {
@@ -88,6 +88,11 @@
<field name="tax_src_id" ref="tvad_09"/>
<field name="tax_dest_id" ref="tvatip09"/>
</record>
<record id="afptt_inverse_11" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_2"/>
<field name="tax_src_id" ref="tvad_19"/>
<field name="tax_dest_id" ref="tvatip19"/>
</record>
<record id="afptt_inverse_9" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_2"/>
<field name="tax_src_id" ref="tvad_20"/>
@@ -147,6 +152,11 @@
<field name="tax_src_id" ref="tvad_09"/>
<field name="tax_dest_id" ref="tvati_intrap9"/>
</record>
<record id="afptt_intracom_11" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_3"/>
<field name="tax_src_id" ref="tvad_19"/>
<field name="tax_dest_id" ref="tvati_intrap19"/>
</record>
<record id="afptt_intracom_13" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_3"/>
<field name="tax_src_id" ref="tvad_20"/>
@@ -207,6 +217,11 @@
<field name="tax_src_id" ref="tvad_09"/>
<field name="tax_dest_id" ref="tvati_intrap9"/>
</record>
<record id="afptt_intracoms_11" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_4"/>
<field name="tax_src_id" ref="tvad_19"/>
<field name="tax_dest_id" ref="tvati_intrap19"/>
</record>
<record id="afptt_intracoms_13" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_4"/>
<field name="tax_src_id" ref="tvad_20"/>
+98 -4
View File
@@ -135,6 +135,21 @@
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_tva_taxare_inversa_colectat_19" model="account.account.tag">
<field name="name">TVA Taxare Inversa Colectat 19%</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_tva_taxare_inversa_deductibil_19" model="account.account.tag">
<field name="name">TVA Taxare Inversa Deductibil 19%</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_tva_taxare_inversa_19" model="account.account.tag">
<field name="name">TVA Taxare Inversa 19%</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_tva_taxare_inversa_colectat_20" model="account.account.tag">
<field name="name">TVA Taxare Inversa Colectat 20%</field>
<field name="applicability">taxes</field>
@@ -210,6 +225,21 @@
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_tva_intracomunitara_achizitii_colectat_19" model="account.account.tag">
<field name="name">TVA Intracomunitara Achizitii Colectat 19%</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_tva_intracomunitara_achizitii_deductibil_19" model="account.account.tag">
<field name="name">TVA Intracomunitara Achizitii Deductibil 19%</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_tva_intracomunitara_achizitii_19" model="account.account.tag">
<field name="name">TVA Intracomunitara Achizitii 19%</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_tva_intracomunitara_achizitii_colectat_20" model="account.account.tag">
<field name="name">TVA Intracomunitara Achizitii Colectat 20%</field>
<field name="applicability">taxes</field>
@@ -281,7 +311,7 @@
<record id="tvac_19" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="sequence">11</field>
<field name="sequence">9</field>
<field name="name">TVA colectat 19%</field>
<field name="description">TVA colectat 19%</field>
<field name="amount">19</field>
@@ -307,7 +337,7 @@
<record id="tvac_24" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="sequence">9</field>
<field name="sequence">11</field>
<field name="name">TVA colectat 24%</field>
<field name="description">TVA colectat 24%</field>
<field name="amount">24</field>
@@ -359,7 +389,7 @@
<record id="tvad_19" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="sequence">23</field>
<field name="sequence">19</field>
<field name="name">TVA deductibil 19%</field>
<field name="description">TVA deductibil 19%</field>
<field name="amount">19</field>
@@ -385,7 +415,7 @@
<record id="tvad_24" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="sequence">19</field>
<field name="sequence">23</field>
<field name="name">TVA deductibil 24%</field>
<field name="description">TVA deductibil 24%</field>
<field name="amount">24</field>
@@ -506,6 +536,38 @@
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_tva_taxare_inversa_9')])]"/>
</record>
<record id="tvatip19-c" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="name">TVA Taxare Inversa Colectat 19%</field>
<field name="amount">-19</field>
<field name="amount_type">percent</field>
<field name="type_tax_use">none</field>
<field name="refund_account_id" ref="pcg_4427"/>
<field name="account_id" ref="pcg_4427"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_tva_taxare_inversa_colectat_19')])]"/>
</record>
<record id="tvatip19-d" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="name">TVA Taxare Inversa Deductibil 19%</field>
<field name="amount">19</field>
<field name="amount_type">percent</field>
<field name="type_tax_use">none</field>
<field name="refund_account_id" ref="pcg_4426"/>
<field name="account_id" ref="pcg_4426"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_tva_taxare_inversa_deductibil_19')])]"/>
</record>
<record id="tvatip19" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="sequence">32</field>
<field name="name">TVA Taxare Inversa 19%</field>
<field name="description">TVA Taxare Inversa 19%</field>
<field name="amount">1</field>
<field name="amount_type">group</field>
<field name="type_tax_use">purchase</field>
<field name="children_tax_ids" eval="[(6, 0, [ref('tvatip19-d'), ref('tvatip19-c')])]"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_tva_taxare_inversa_19')])]"/>
</record>
<record id="tvatip20-c" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="name">TVA Taxare Inversa Colectat 20%</field>
@@ -730,6 +792,38 @@
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_tva_intracomunitara_achizitii_9')])]"/>
</record>
<record id="tvati_intrap19c" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="name">TVA Intracomunitara Achizitii Colectat 19%</field>
<field name="amount">-19</field>
<field name="amount_type">percent</field>
<field name="type_tax_use">none</field>
<field name="refund_account_id" ref="pcg_4427"/>
<field name="account_id" ref="pcg_4427"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_tva_intracomunitara_achizitii_colectat_19')])]"/>
</record>
<record id="tvati_intrap19d" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="name">TVA Intracomunitara Achizitii Deductibil 19%</field>
<field name="price_include" eval="0"/>
<field name="amount">19</field>
<field name="amount_type">percent</field>
<field name="type_tax_use">none</field>
<field name="refund_account_id" ref="pcg_4426"/>
<field name="account_id" ref="pcg_4426"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_tva_intracomunitara_achizitii_deductibil_19')])]"/>
</record>
<record id="tvati_intrap19" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="sequence">62</field>
<field name="name">TVA Intracomunitara Achizitii 19%</field>
<field name="amount">1</field>
<field name="amount_type">group</field>
<field name="type_tax_use">purchase</field>
<field name="children_tax_ids" eval="[(6, 0, [ref('tvati_intrap19d'), ref('tvati_intrap19c')])]"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_tva_intracomunitara_achizitii_19')])]"/>
</record>
<record id="tvati_intrap20c" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="name">TVA Intracomunitara Achizitii Colectat 20%</field>
+1
View File
@@ -15,6 +15,7 @@
'data/maintenance_data.xml',
'views/maintenance_views.xml',
'views/maintenance_templates.xml',
'data/maintenance_cron.xml',
],
'demo': ['data/maintenance_demo.xml'],
'installable': True,
+67 -23
View File
@@ -144,17 +144,48 @@ class MaintenanceEquipment(models.Model):
maintenance_team_id = fields.Many2one('maintenance.team', string='Maintenance Team')
maintenance_duration = fields.Float(help="Maintenance Duration in minutes and seconds.")
@api.depends('period', 'maintenance_open_count', 'maintenance_ids.request_date')
@api.depends('period', 'maintenance_ids.request_date', 'maintenance_ids.close_date')
def _compute_next_maintenance(self):
for equipment in self:
create_date = equipment.create_date and datetime.strptime(equipment.create_date, DEFAULT_SERVER_DATETIME_FORMAT)
if equipment.period:
next_date = create_date
if equipment.maintenance_ids:
maintenance = equipment.maintenance_ids.sorted(lambda x: x.request_date)[0]
next_date = maintenance.request_date and datetime.strptime(maintenance.request_date, DEFAULT_SERVER_DATE_FORMAT) or create_date
equipment.next_action_date = next_date and (next_date + timedelta(days=equipment.period)).strftime(DEFAULT_SERVER_DATE_FORMAT)
date_now = fields.Date.context_today(self)
for equipment in self.filtered(lambda x: x.period > 0):
next_maintenance_todo = self.env['maintenance.request'].search([
('equipment_id', '=', equipment.id),
('maintenance_type', '=', 'preventive'),
('stage_id.done', '!=', True),
('close_date', '=', False)], order="request_date asc", limit=1)
last_maintenance_done = self.env['maintenance.request'].search([
('equipment_id', '=', equipment.id),
('maintenance_type', '=', 'preventive'),
('stage_id.done', '=', True),
('close_date', '!=', False)], order="close_date desc", limit=1)
if next_maintenance_todo and last_maintenance_done:
next_date = next_maintenance_todo.request_date
date_gap = fields.Date.from_string(next_maintenance_todo.request_date) - fields.Date.from_string(last_maintenance_done.close_date)
# If the gap between the last_maintenance_done and the next_maintenance_todo one is bigger than 2 times the period and next request is in the future
# We use 2 times the period to avoid creation too closed request from a manually one created
if date_gap > timedelta(0) and date_gap > timedelta(days=equipment.period) * 2 and fields.Date.from_string(next_maintenance_todo.request_date) > fields.Date.from_string(date_now):
# If the new date still in the past, we set it for today
if fields.Date.from_string(last_maintenance_done.close_date) + timedelta(days=equipment.period) < fields.Date.from_string(date_now):
next_date = date_now
else:
next_date = fields.Date.to_string(fields.Date.from_string(last_maintenance_done.close_date) + timedelta(days=equipment.period))
elif next_maintenance_todo:
next_date = next_maintenance_todo.request_date
date_gap = fields.Date.from_string(next_maintenance_todo.request_date) - fields.Date.from_string(date_now)
# If next maintenance to do is in the future, and in more than 2 times the period, we insert an new request
# We use 2 times the period to avoid creation too closed request from a manually one created
if date_gap > timedelta(0) and date_gap > timedelta(days=equipment.period) * 2:
next_date = fields.Date.to_string(fields.Date.from_string(date_now)+timedelta(days=equipment.period))
elif last_maintenance_done:
next_date = fields.Date.from_string(last_maintenance_done.close_date)+timedelta(days=equipment.period)
# If when we add the period to the last maintenance done and we still in past, we plan it for today
if next_date < fields.Date.from_string(date_now):
next_date = date_now
else:
next_date = fields.Date.to_string(fields.Date.from_string(date_now) + timedelta(days=equipment.period))
equipment.next_action_date = next_date
@api.one
@api.depends('maintenance_ids.stage_id.done')
def _compute_maintenance_count(self):
@@ -190,18 +221,31 @@ class MaintenanceEquipment(models.Model):
category_ids = categories._search([], order=order, access_rights_uid=SUPERUSER_ID)
return categories.browse(category_ids)
def _create_new_request(self, date):
self.ensure_one()
self.env['maintenance.request'].create({
'name': _('Preventive Maintenance - %s') % self.name,
'request_date': date,
'schedule_date': date,
'category_id': self.category_id.id,
'equipment_id': self.id,
'maintenance_type': 'preventive',
'owner_user_id': self.owner_user_id.id,
'technician_user_id': self.technician_user_id.id,
})
@api.model
def _cron_generate_requests(self):
for equipment in self.search([]):
if equipment.period and equipment.next_action_date == date.today().strftime(DEFAULT_SERVER_DATE_FORMAT):
self.env['maintenance.request'].create({
'name': _('Preventive Maintenance - %s' % equipment.next_action_date),
'request_date': equipment.next_action_date,
'category_id': equipment.category_id.id,
'equipment_id': equipment.id,
'maintenance_type': 'preventive',
})
"""
Generates maintenance request on the next_action_date or today if none exists
"""
for equipment in self.search([('period', '>', 0)]):
next_requests = self.env['maintenance.request'].search([('stage_id.done', '=', False),
('equipment_id', '=', equipment.id),
('maintenance_type', '=', 'preventive'),
('request_date', '=', equipment.next_action_date)])
if not next_requests:
equipment._create_new_request(equipment.next_action_date)
class MaintenanceRequest(models.Model):
_name = 'maintenance.request'
@@ -227,8 +271,8 @@ class MaintenanceRequest(models.Model):
name = fields.Char('Subjects', required=True)
description = fields.Text('Description')
request_date = fields.Date('Request Date', track_visibility='onchange', default=fields.Date.context_today)
request_date = fields.Date('Request Date', track_visibility='onchange', default=fields.Date.context_today,
help="Date requested for the maintenance to happen")
owner_user_id = fields.Many2one('res.users', string='Created by', default=lambda s: s.env.uid)
category_id = fields.Many2one('maintenance.equipment.category', related='equipment_id.category_id', string='Category', store=True, readonly=True)
equipment_id = fields.Many2one('maintenance.equipment', string='Equipment', index=True)
@@ -237,13 +281,13 @@ class MaintenanceRequest(models.Model):
group_expand='_read_group_stage_ids', default=_default_stage)
priority = fields.Selection([('0', 'Very Low'), ('1', 'Low'), ('2', 'Normal'), ('3', 'High')], string='Priority')
color = fields.Integer('Color Index')
close_date = fields.Date('Close Date')
close_date = fields.Date('Close Date', help="Date the maintenance was finished. ")
kanban_state = fields.Selection([('normal', 'In Progress'), ('blocked', 'Blocked'), ('done', 'Ready for next stage')],
string='Kanban State', required=True, default='normal', track_visibility='onchange')
# active = fields.Boolean(default=True, help="Set active to false to hide the maintenance request without deleting it.")
archive = fields.Boolean(default=False, help="Set archive to true to hide the maintenance request without deleting it.")
maintenance_type = fields.Selection([('corrective', 'Corrective'), ('preventive', 'Preventive')], string='Maintenance Type', default="corrective")
schedule_date = fields.Datetime('Scheduled Date')
schedule_date = fields.Datetime('Scheduled Date', help="Date the maintenance team plans the maintenance. It should not differ much from the Request Date. ")
maintenance_team_id = fields.Many2one('maintenance.team', string='Team', required=True, default=_get_default_team_id)
duration = fields.Float(help="Duration in minutes and seconds.")
+32 -2
View File
@@ -4,7 +4,8 @@
import time
from odoo.tests.common import TransactionCase
from dateutil import relativedelta
import datetime
class TestEquipment(TransactionCase):
""" Test used to check that when doing equipment/maintenance_request/equipment_category creation."""
@@ -36,7 +37,7 @@ class TestEquipment(TransactionCase):
groups_id=[(6, 0, [res_manager.id])]
))
def test_equipment_request_category(self):
def test_10_equipment_request_category(self):
# Create a new equipment
equipment_01 = self.equipment.sudo(self.manager).create({
@@ -75,3 +76,32 @@ class TestEquipment(TransactionCase):
# I check that maintenance request is in the "In Progress" stage
self.assertEquals(maintenance_request_01.stage_id.id, self.ref('maintenance.stage_1'))
def test_20_cron(self):
""" Check the cron creates the necessary preventive maintenance requests"""
equipment_cron = self.equipment.create({
'name': 'High Maintenance Monitor because of Color Calibration',
'category_id': self.ref('maintenance.equipment_monitor'),
'technician_user_id': self.ref('base.user_root'),
'owner_user_id': self.user.id,
'assign_date': time.strftime('%Y-%m-%d'),
'period': 7,
'color': 3,
})
maintenance_request_cron = self.maintenance_request.create({
'name': 'Need a special calibration',
'technician_user_id': self.user.id,
'request_date': (datetime.datetime.now() + relativedelta.relativedelta(days=7)).strftime('%Y-%m-%d'),
'maintenance_type': 'preventive',
'owner_user_id': self.user.id,
'equipment_id': equipment_cron.id,
'color': 7,
'stage_id': self.ref('maintenance.stage_0'),
'maintenance_team_id': self.ref('maintenance.equipment_team_maintenance')
})
self.env['maintenance.equipment']._cron_generate_requests()
# As it is generating the requests for one month in advance, we should have 4 requests in total
tot_requests = self.maintenance_request.search([('equipment_id', '=', equipment_cron.id)])
self.assertEqual(len(tot_requests), 1, 'The cron should have generated just 1 request for the High Maintenance Monitor.')
@@ -78,7 +78,7 @@
<field name="owner_user_id" string="Requested By"/>
<field name="equipment_id" context="{'default_category_id':category_id}"/>
<field name="category_id" groups="maintenance.group_equipment_manager"/>
<field name="request_date" groups="base.group_no_one"/>
<field name="request_date"/>
<field name="close_date" groups="base.group_no_one"/>
<field name="archive" invisible="1"/>
<field name="maintenance_type" widget="radio"/>
@@ -416,7 +416,7 @@
<div class="oe_kanban_footer_left mt4 label label-danger" t-if="record.maintenance_open_count.raw_value" >
<t t-raw="record.maintenance_open_count.raw_value"/> Request
</div>
<div class="oe_kanban_footer_left mt4 label label-default" t-if="record.maintenance_open_count.raw_value == 0 and record.next_action_date.raw_value" >
<div class="oe_kanban_footer_left mt4 label label-default" t-if="record.next_action_date.raw_value" >
<t t-raw="moment(record.next_action_date.raw_value).format('MMMM Do')"/>
</div>
</div>
+1
View File
@@ -323,6 +323,7 @@ class MrpProduction(models.Model):
data = {
'name': self.name,
'date': self.date_planned_start,
'date_expected': self.date_planned_start,
'bom_line_id': bom_line.id,
'product_id': bom_line.product_id.id,
'product_uom_qty': quantity,
+3 -3
View File
@@ -81,14 +81,14 @@ class StockMove(models.Model):
store=True,
help='Technical Field to order moves') # TDE: what ?
@api.multi
@api.depends('state', 'product_uom_qty', 'reserved_availability')
def _qty_available(self):
for move in self:
# For consumables, state is available so availability = qty to do
if move.state == 'assigned':
move.quantity_available = move.product_uom_qty
else:
move.quantity_available = move.reserved_availability
elif move.product_id.uom_id and move.product_uom:
move.quantity_available = move.product_id.uom_id._compute_quantity(move.reserved_availability, move.product_uom)
@api.multi
@api.depends('move_lot_ids', 'move_lot_ids.quantity_done', 'quantity_done_store')
@@ -17,7 +17,7 @@
<field name="product_uom_id" readonly="1"/>
</div>
<field name="product_tracking" invisible="1"/>
<field name="lot_id" attrs="{'invisible': [('product_tracking', '=', 'none')]}" domain="[('product_id','=',product_id)]" context="{'default_product_id': product_id}"/>
<field name="lot_id" required="1" attrs="{'invisible': [('product_tracking', '=', 'none')]}" domain="[('product_id','=',product_id)]" context="{'default_product_id': product_id}"/>
</group>
</group>
<group>
+9
View File
@@ -198,6 +198,7 @@ class PosOrder(models.Model):
grouped_data = {}
have_to_group_by = session and session.config_id.group_by or False
rounding_method = session and session.config_id.company_id.tax_calculation_rounding_method
for order in self.filtered(lambda o: not o.account_move or order.state == 'paid'):
current_company = order.sale_journal.company_id
@@ -296,6 +297,14 @@ class PosOrder(models.Model):
'partner_id': partner_id
})
# round tax lines per order
if rounding_method == 'round_globally':
for group_key, group_value in grouped_data.iteritems():
if group_key[0] == 'tax':
for line in group_value:
line['credit'] = cur.round(line['credit'])
line['debit'] = cur.round(line['debit'])
# counterpart
insert_data('counter_part', {
'name': _("Trade Receivables"), # order.name,
+10 -4
View File
@@ -278,13 +278,19 @@ var DebugWidget = PosBaseWidget.extend({
});
this.$('.button.export_unpaid_orders').click(function(){
self.gui.download_file(self.pos.export_unpaid_orders(),
"unpaid_orders_" + (new Date()).toUTCString().replace(/\ /g,'_') + '.json');
self.gui.prepare_download_link(
self.pos.export_unpaid_orders(),
_t("unpaid orders") + ' ' + moment().format('YYYY-MM-DD-HH-mm-ss') + '.json',
".export_unpaid_orders", ".download_unpaid_orders"
);
});
this.$('.button.export_paid_orders').click(function() {
self.gui.download_file(self.pos.export_paid_orders(),
"paid_orders_" + (new Date()).toUTCString().replace(/\ /g,'_') + '.json');
self.gui.prepare_download_link(
self.pos.export_paid_orders(),
_t("paid orders") + ' ' + moment().format('YYYY-MM-DD-HH-mm-ss') + '.json',
".export_paid_orders", ".download_paid_orders"
);
});
this.$('.button.import_orders input').on('change', function(event) {
+29 -7
View File
@@ -362,7 +362,34 @@ var Gui = core.Class.extend({
// if 'contents' is not a string, it is converted into
// a JSON representation of the contents.
// TODO: remove me in master: deprecated in favor of prepare_download_link
// this method is kept for backward compatibility but is likely not going
// to work as many browsers do to not accept fake click events on links
download_file: function(contents, name) {
href_params = this.prepare_file_blob(contents,name);
var evt = document.createEvent("HTMLEvents");
evt.initEvent("click");
$("<a>",href_params).get(0).dispatchEvent(evt);
},
prepare_download_link: function(contents, filename, src, target) {
var href_params = this.prepare_file_blob(contents, filename);
$(target).parent().attr(href_params);
$(src).addClass('oe_hidden');
$(target).removeClass('oe_hidden');
// hide again after click
$(target).click(function() {
$(src).removeClass('oe_hidden');
$(this).addClass('oe_hidden');
});
},
prepare_file_blob: function(contents, name) {
var URL = window.URL || window.webkitURL;
if (typeof contents !== 'string') {
@@ -371,13 +398,8 @@ var Gui = core.Class.extend({
var blob = new Blob([contents]);
var evt = document.createEvent("HTMLEvents");
evt.initEvent("click");
$("<a>",{
download: name || 'document.txt',
href: URL.createObjectURL(blob),
}).get(0).dispatchEvent(evt);
return {download: name || 'document.txt',
href: URL.createObjectURL(blob),}
},
/* ---- Gui: EMAILS ---- */
+3 -1
View File
@@ -105,7 +105,9 @@ var ErrorTracebackPopupWidget = ErrorPopupWidget.extend({
this._super(opts);
this.$('.download').off('click').click(function(){
self.gui.download_file(self.options.body,'traceback.txt');
self.gui.prepare_download_link(self.options.body,
_t('error') + ' ' + moment().format('YYYY-MM-DD-HH-mm-ss') + '.txt',
'.download', '.download_error_file');
});
this.$('.email').off('click').click(function(){
+6 -1
View File
@@ -336,7 +336,7 @@
</div>
<div class='client-detail'>
<span class='label'>Country</span>
<select class='detail client-address-country' name='country_id'>
<select class='detail client-address-country needsclick' name='country_id'>
<option value=''>None</option>
<t t-foreach='widget.pos.countries' t-as='country'>
<option t-att-value='country.id' t-att-selected="partner.country_id ? ((country.id === partner.country_id[0]) ? true : undefined) : undefined">
@@ -975,6 +975,9 @@
<div class="button cancel">
Ok
</div>
<a><div class="button icon download_error_file oe_hidden">
<i class="fa fa-arrow-down"></i>
</div></a>
<div class="button icon download">
<i class="fa fa-download"></i>
</div>
@@ -1308,7 +1311,9 @@
<li class="button delete_orders">Delete Paid Orders</li>
<li class="button delete_unpaid_orders">Delete Unpaid Orders</li>
<li class="button export_paid_orders">Export Paid Orders</li>
<a><li class="button download_paid_orders oe_hidden">Download Paid Orders</li></a>
<li class="button export_unpaid_orders">Export Unpaid Orders</li>
<a><li class="button download_unpaid_orders oe_hidden">Download Unpaid Orders</li></a>
<li class="button import_orders" style="position:relative">
Import Orders
<input type="file" style="opacity:0;position:absolute;top:0;left:0;right:0;bottom:0;margin:0;cursor:pointer"/>
@@ -4,7 +4,7 @@
<field name="name">report.pos.order.pivot</field>
<field name="model">report.pos.order</field>
<field name="arch" type="xml">
<pivot string="Point of Sale Analysis">
<pivot string="Point of Sale Analysis" disable_linking="True">
<field name="product_categ_id" type="row"/>
<field name="date" interval="month" type="col"/>
<field name="product_qty" type="measure"/>
+2
View File
@@ -44,6 +44,7 @@
<group>
<group name="group_general">
<field name="type"/>
<field name="categ_id" string="Internal Category"/>
</group>
<group name="group_standard_price">
<field name="list_price" widget='monetary' options="{'currency_field': 'currency_id'}"/>
@@ -310,6 +311,7 @@
<field name="item_ids" position="replace">
<field name="pricelist_item_ids" widget="many2many">
<tree string="Pricelist Items">
<field name="pricelist_id"/>
<field name="name" string="Applicable On"/>
<field name="min_quantity"/>
<field name="date_start"/>
+1
View File
@@ -448,6 +448,7 @@ class PurchaseOrder(models.Model):
result = action.read()[0]
#override the context to get rid of the default filtering on picking type
result.pop('id', None)
result['context'] = {}
pick_ids = sum([order.picking_ids.ids for order in self], [])
#choose the view_mode accordingly
@@ -17,7 +17,7 @@ class ir_actions_report(models.Model):
"""
self.ensure_one()
action_ref = self.env.ref('base.action_ui_view')
if not action_ref:
if not action_ref or len(self.report_name.split('.')) < 2:
return False
action_data = action_ref.read()[0]
action_data['domain'] = [('name', 'ilike', self.report_name.split('.')[1]), ('type', '=', 'qweb')]
@@ -29,9 +29,11 @@
<field name="model">product.template</field>
<field name="inherit_id" ref="stock.view_template_property_form"/>
<field name="arch" type="xml">
<field name="categ_id" position="after">
<field name="intrastat_id"/>
</field>
<group name="group_lots_and_weight" position="after">
<group>
<field name="intrastat_id"/>
</group>
</group>
</field>
</record>
+4 -2
View File
@@ -506,10 +506,12 @@ class SaleOrder(models.Model):
for tax in line.tax_id:
group = tax.tax_group_id
res.setdefault(group, 0.0)
amount = tax.compute_all(line.price_reduce + base_tax, quantity=line.product_uom_qty)['taxes'][0]['amount']
amount = tax.compute_all(line.price_reduce + base_tax, quantity=line.product_uom_qty,
product=line.product_id, partner=line.order_partner_id)['taxes'][0]['amount']
res[group] += amount
if tax.include_base_amount:
base_tax += tax.compute_all(line.price_reduce + base_tax, quantity=1)['taxes'][0]['amount']
base_tax += tax.compute_all(line.price_reduce + base_tax, quantity=1, product=line.product_id,
partner=line.order_partner_id)['taxes'][0]['amount']
res = sorted(res.items(), key=lambda l: l[0].sequence)
res = map(lambda l: (l[0].name, l[1]), res)
return res
@@ -84,7 +84,7 @@
</a>
</td>
<td t-if="show_demo or (values and values['closing']['overdue'] != 0)" class="o_warning" title="To Opportunities">
<a href="#" class="o_dashboard_action" name="crm.action_your_pipeline" data-context="{'search_default_activities_overdue':'1'}">
<a href="#" class="o_dashboard_action" name="crm.action_your_pipeline" data-context="{'search_default_overdue_opp':'1'}">
<t t-if="!show_demo">
<t t-esc="values['closing']['overdue']"/> exp. closing
</t>
+3 -5
View File
@@ -34,8 +34,7 @@ class PackOperation(models.Model):
product_qty = fields.Float('To Do', default=0.0, digits=dp.get_precision('Product Unit of Measure'), required=True)
ordered_qty = fields.Float('Ordered Quantity', digits=dp.get_precision('Product Unit of Measure'))
qty_done = fields.Float('Done', default=0.0, digits=dp.get_precision('Product Unit of Measure'))
# TDE FIXME: what what what what ??
is_done = fields.Boolean(compute='_compute_is_done', inverse='_set_is_done', string='Done', oldname='processed_boolean')
is_done = fields.Boolean(compute='_compute_is_done', string='Done', readonly=False, oldname='processed_boolean')
package_id = fields.Many2one('stock.quant.package', 'Source Package')
pack_lot_ids = fields.One2many('stock.pack.operation.lot', 'operation_id', 'Lots/Serial Numbers Used')
result_package_id = fields.Many2one(
@@ -73,9 +72,8 @@ class PackOperation(models.Model):
def _compute_is_done(self):
self.is_done = self.qty_done > 0.0
@api.one
def _set_is_done(self):
# TDE FIXME: whuuuut ???
@api.onchange('is_done')
def on_change_is_done(self):
if not self.product_id:
if self.is_done and self.qty_done == 0:
self.qty_done = 1.0
+4 -2
View File
@@ -1366,7 +1366,8 @@ class TestStockFlow(TestStockCommon):
# set the pack in pack operation to 'done'
for pack in picking_pack.pack_operation_pack_ids:
pack.qty_done = 1.0
pack.is_done = True
pack.on_change_is_done()
# Put in a pack
picking_pack.put_in_pack()
@@ -1384,7 +1385,8 @@ class TestStockFlow(TestStockCommon):
# set the pack in pack operation to 'done'
for pack in picking_out.pack_operation_pack_ids:
pack.qty_done = 1.0
pack.is_done = True
pack.on_change_is_done()
# Validate picking
picking_out.do_new_transfer()
-3
View File
@@ -140,9 +140,6 @@
<field name="volume" string="Volume"/>
</div>
</group>
<group>
<field name="categ_id" string="Internal Category"/>
</group>
<group name="stock_property" groups="base.group_no_one">
<field name="property_stock_procurement" domain="[('usage','=','procurement')]"/>
<field name="property_stock_production" domain="[('usage','=','production')]"/>
@@ -15,7 +15,6 @@
<div class="oe_button_box" name="button_box">
<button name="%(lot_open_quants)d" icon="fa-arrows" class="oe_stat_button" string="Locate" type="action" attrs="{'invisible': [('quant_ids','=',[])]}"/>
<button name="action_traceability" icon="fa-arrow-up" class="oe_stat_button" string="Traceability" type="object" attrs="{'invisible': [('quant_ids','=',[])]}"/>
<field name="quant_ids" invisible="True"/>
</div>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
+1 -1
View File
@@ -79,7 +79,7 @@ class StockHistory(models.Model):
product_template_id,
SUM(quantity) as quantity,
date,
SUM(price_unit_on_quant * quantity) / SUM(quantity) as price_unit_on_quant,
COALESCE(SUM(price_unit_on_quant * quantity) / NULLIF(SUM(quantity), 0), 0) as price_unit_on_quant,
source,
string_agg(DISTINCT serial_number, ', ' ORDER BY serial_number) AS serial_number
FROM
+1 -1
View File
@@ -638,7 +638,7 @@ class SurveyQuestion(models.Model):
if self.comments_allowed:
comment_answer = answer_candidates.pop(("%s_%s" % (answer_tag, 'comment')), '').strip()
# Preventing answers with blank value
if all([True if not answer.strip() else False for answer in answer_candidates.values()]):
if all([True if not answer.strip() else False for answer in answer_candidates.values()]) and answer_candidates:
errors.update({answer_tag: self.constr_error_msg})
# There is no answer neither comments (if comments count as answer)
if not answer_candidates and self.comment_count_as_answer and (not comment_flag or not comment_answer):
@@ -86,8 +86,11 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
* Init a session, reloads from cookie, if it exists
*/
session_init: function () {
var def = this.session_reload();
if (this.is_frontend) return def;
var self = this;
return this.session_reload().then(function() {
return def.then(function() {
var modules = self.module_list.join(',');
var deferred = self.load_qweb(modules);
if(self.session_is_valid()) {
@@ -26,7 +26,7 @@ var M2ODialog = Dialog.extend({
title: _.str.sprintf(_t("Create a %s"), parent.string),
size: 'medium',
buttons: [
{text: _t('Create'), classes: 'btn-primary', click: function() {
{text: _t('Create'), classes: 'btn-primary', click: function(e) {
if (this.$("input").val() !== ''){
this.getParent()._quick_create(this.$("input").val());
this.close();
@@ -997,7 +997,9 @@ var X2ManyListView = ListView.extend({
*/
var X2ManyList = ListView.List.extend({
pad_table_to: function (count) {
if (!this.view.is_action_enabled('create') || this.view.x2m.get('effective_readonly')) {
var ftype = this.view.x2m.field.type;
var is_readonly = this.view.x2m.get('effective_readonly');
if (is_readonly || (ftype === 'one2many' && !this.view.is_action_enabled('create'))) {
this._super(count);
return;
}
@@ -1304,7 +1306,7 @@ var Many2ManyListView = X2ManyListView.extend({
context: this.x2m.build_context(),
title: _t("Add: ") + this.x2m.string,
alternative_form_view: this.x2m.field.views ? this.x2m.field.views.form : undefined,
no_create: this.x2m.options.no_create,
no_create: this.x2m.options.no_create || !this.is_action_enabled('create'),
on_selected: function(element_ids) {
return self.x2m.data_link_multi(element_ids).then(function() {
self.x2m.reload_current_view();
+1 -1
View File
@@ -1508,7 +1508,7 @@ ListView.Groups = Class.extend({
// drag and drop enabled if list is not sorted (unless it is sorted by
// its sequence field (ASC)), and there is a visible column with
// @widget=handle or "sequence" column in the view.
if ((dataset.sort && [seqname, seqname + 'ASC', ''].indexOf(dataset.sort()) === -1)
if ((dataset.sort && [seqname, seqname + ' ASC', ''].indexOf(dataset.sort()) === -1)
|| !_(this.columns).findWhere({'name': seqname})) {
return;
}
@@ -57,6 +57,7 @@ var PivotView = View.extend({
this.headers = {};
this.cells = {};
this.has_data = false;
this.$buttons = $();
this.last_header_selected = null;
this.sorted_column = {};
@@ -34,20 +34,16 @@ class WebsiteSaleDigital(website_account):
return response
order = response.qcontext['order']
invoiced_lines = request.env['account.invoice.line'].sudo().search([('invoice_id', 'in', order.invoice_ids.ids), ('invoice_id.state', '=', 'paid')])
products = invoiced_lines.mapped('product_id') | order.order_line.filtered(lambda r: not r.price_subtotal).mapped('product_id')
purchased_products_attachments = {}
for il in invoiced_lines:
product = il.product_id
# Ignore products that do not have digital content
if not product.product_tmpl_id.type == 'digital':
continue
for product in products:
# Search for product attachments
Attachment = request.env['ir.attachment']
product_id = product.id
template = product.product_tmpl_id
att = Attachment.search_read(
domain=['|', '&', ('res_model', '=', product._name), ('res_id', '=', product_id), '&', ('res_model', '=', template._name), ('res_id', '=', template.id)],
domain=['|', '&', ('res_model', '=', product._name), ('res_id', '=', product_id), '&', ('res_model', '=', template._name), '&', ('res_id', '=', template.id), ('product_downloadable', '=', True)],
fields=['name', 'write_date'],
order='write_date desc',
)
@@ -13,7 +13,13 @@ class AccountInvoiceLine(models.Model):
# Get paid invoices
purchases = self.sudo().search_read(
domain=[('invoice_id.state', '=', 'paid'), ('invoice_id.partner_id', '=', partner.id), ('product_id.product_tmpl_id.type', '=', 'digital')],
domain=[('invoice_id.state', '=', 'paid'), ('invoice_id.partner_id', '=', partner.id)],
fields=['product_id'],
)
# Get free products
purchases += self.env['sale.order.line'].sudo().search_read(
domain=[('price_subtotal', '=', 0.0), ('order_id.partner_id', '=', partner.id)],
fields=['product_id'],
)
+17 -13
View File
@@ -494,11 +494,12 @@ class Slide(models.Model):
# --------------------------------------------------
@api.model
def _fetch_data(self, base_url, data, content_type=False):
def _fetch_data(self, base_url, data, content_type=False, extra_params=False):
result = {'values': dict()}
try:
if data:
base_url = base_url + '?%s' % urlencode(data)
sep = '?' if not extra_params else '&'
base_url = base_url + '%s%s' % (sep, urlencode(data))
req = urllib2.Request(base_url)
content = urllib2.urlopen(req).read()
if content_type == 'json':
@@ -566,12 +567,13 @@ class Slide(models.Model):
def _parse_google_document(self, document_id, only_preview_fields):
def get_slide_type(vals):
# TDE FIXME: WTF ??
image = Image.open(io.BytesIO(vals['image'].decode('base64')))
width, height = image.size
if height > width:
return 'document'
else:
return 'presentation'
slide_type = 'presentation'
if vals.get('image'):
image = Image.open(io.BytesIO(vals['image'].decode('base64')))
width, height = image.size
if height > width:
return 'document'
return slide_type
# Google drive doesn't use a simple API key to access the data, but requires an access
# token. However, this token is generated in module google_drive, which is not in the
@@ -609,12 +611,14 @@ class Slide(models.Model):
values['datas'] = values['image']
values['slide_type'] = 'infographic'
elif google_values['mimeType'].startswith('application/vnd.google-apps'):
values['datas'] = self._fetch_data(google_values['exportLinks']['application/pdf'], {}, 'pdf')['values']
values['slide_type'] = get_slide_type(values)
if google_values['exportLinks'].get('text/plain'):
values['index_content'] = self._fetch_data(google_values['exportLinks']['text/plain'], {})['values']
if google_values['exportLinks'].get('text/csv'):
values['index_content'] = self._fetch_data(google_values['exportLinks']['text/csv'], {})['values']
if 'exportLinks' in google_values:
values['datas'] = self._fetch_data(google_values['exportLinks']['application/pdf'], params, 'pdf', extra_params=True)['values']
# Content indexing
if google_values['exportLinks'].get('text/plain'):
values['index_content'] = self._fetch_data(google_values['exportLinks']['text/plain'], params, extra_params=True)['values']
elif google_values['exportLinks'].get('text/csv'):
values['index_content'] = self._fetch_data(google_values['exportLinks']['text/csv'], params, extra_params=True)['values']
elif google_values['mimeType'] == 'application/pdf':
# TODO: Google Drive PDF document doesn't provide plain text transcript
values['datas'] = self._fetch_data(google_values['webContentLink'], {}, 'pdf')['values']
+17
View File
@@ -0,0 +1,17 @@
Belgium, 30/01/2017
Somko 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,
Frederik Santens info@somko.be https://github.com/somko
List of contributors:
Frederik Santens info@somko.be https://github.com/somko
Thomas Vanmellaerts thomas.vanmellaerts@somko.be https://github.com/thomassomko
Kasper Declercq kasper.declercq@somko.be https://github.com/kasperdeclercq
+11
View File
@@ -0,0 +1,11 @@
China, 2017-02-06
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,
Yue Qin 602559817@qq.com https://github.com/yueqin
+2 -2
View File
@@ -207,9 +207,9 @@ RPM
.. warning::
with RHEL-based distributions (RHEL, CenOS, Scientific Linux), EPEL_ must
with RHEL-based distributions (RHEL, CentOS, Scientific Linux), EPEL_ must
be added to the distribution's repositories for all of Odoo's
dependencies to be available. For CenOS:
dependencies to be available. For CentOS:
.. code-block:: console
+5 -4
View File
@@ -611,10 +611,11 @@ class IrActionsServer(models.Model):
:type action: browse record
:returns: dict -- evaluation context given to (safe_)safe_eval """
def log(message, level="info"):
self._cr.execute("""
INSERT INTO ir_logging(create_date, create_uid, type, dbname, name, level, message, path, line, func)
VALUES (NOW() at time zone 'UTC', %s, %s, %s, %s, %s, %s, %s, %s, %s)
""", (self.env.uid, 'server', self._cr.dbname, __name__, level, message, "action", action.id, action.name))
with self.pool.cursor() as cr:
cr.execute("""
INSERT INTO ir_logging(create_date, create_uid, type, dbname, name, level, message, path, line, func)
VALUES (NOW() at time zone 'UTC', %s, %s, %s, %s, %s, %s, %s, %s, %s)
""", (self.env.uid, 'server', self._cr.dbname, __name__, level, message, "action", action.id, action.name))
eval_context = super(IrActionsServer, self)._get_eval_context(action=action)
model = self.env[action.model_id.model]
+1 -1
View File
@@ -1340,7 +1340,7 @@ class IrModelData(models.Model):
class WizardModelMenu(models.TransientModel):
_name = 'wizard.ir.model.menu.create'
menu_id = fields.Many2one('ir.ui.menu', string='Parent Menu', required=True)
menu_id = fields.Many2one('ir.ui.menu', string='Parent Menu', required=True, ondelete='cascade')
name = fields.Char(string='Menu Name', required=True)
@api.multi
+1 -1
View File
@@ -55,7 +55,7 @@ class IrUiMenu(models.Model):
if level <= 0:
return '...'
if self.parent_id:
return self.parent_id._get_full_name(level - 1) + MENU_ITEM_SEPARATOR + self.name
return self.parent_id._get_full_name(level - 1) + MENU_ITEM_SEPARATOR + (self.name or "")
else:
return self.name
+5
View File
@@ -430,6 +430,7 @@ class Partner(models.Model):
sync_children = self.child_ids.filtered(lambda c: not c.is_company)
for child in sync_children:
child._commercial_sync_to_children()
sync_children._compute_commercial_partner()
return sync_children.write(sync_vals)
@api.multi
@@ -453,6 +454,10 @@ class Partner(models.Model):
commercial_fields = self._commercial_fields()
if any(field in values for field in commercial_fields):
self._commercial_sync_to_children()
for child in self.child_ids.filtered(lambda c: not c.is_company):
if child.commercial_partner_id != self.commercial_partner_id :
self._commercial_sync_to_children()
break
# 2b. Address fields: sync if address changed
address_fields = self._address_fields()
if any(field in values for field in address_fields):
+1 -1
View File
@@ -2001,7 +2001,7 @@ class BaseModel(object):
prefix_term = lambda prefix, term: ('%s %s' % (prefix, term)) if term else ''
query = """
SELECT min(%(table)s.id) AS id, count(%(table)s.id) AS %(count_field)s %(extra_fields)s
SELECT min("%(table)s".id) AS id, count("%(table)s".id) AS "%(count_field)s" %(extra_fields)s
FROM %(from)s
%(where)s
%(groupby)s