summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2017-02-10 13:17:02 +0100
committerChristophe Simonis <chs@odoo.com>2017-02-10 13:17:02 +0100
commitcbdd0b20dcfe1f46706c631262b4eab9996f4eb7 (patch)
treecfbe4fd881f9b80ed1da3bca84022607095f5f2d /addons
parent3fbaa6c409278014797ce55c6841055b1364d625 (diff)
parentffd4442b06bc679a79b107c72806f57da3500d3f (diff)
[MERGE] forward port branch 10.0 up to ffd4442b06b
Diffstat (limited to 'addons')
-rw-r--r--addons/account/models/account_invoice.py7
-rw-r--r--addons/account/models/account_move.py25
-rw-r--r--addons/account/models/account_payment.py4
-rw-r--r--addons/account/views/account_view.xml3
-rw-r--r--addons/crm/data/sales_config_settings_data.xml4
-rw-r--r--addons/crm/views/crm_lead_views.xml3
-rw-r--r--addons/im_livechat/static/src/less/im_livechat.less1
-rw-r--r--addons/l10n_ro/data/account_fiscal_position_data.xml15
-rw-r--r--addons/l10n_ro/data/account_tax_data.xml102
-rw-r--r--addons/maintenance/__manifest__.py1
-rw-r--r--addons/maintenance/models/maintenance.py90
-rw-r--r--addons/maintenance/tests/test_maintenance.py34
-rw-r--r--addons/maintenance/views/maintenance_views.xml4
-rw-r--r--addons/mrp/models/mrp_production.py1
-rw-r--r--addons/mrp/models/stock_move.py6
-rw-r--r--addons/mrp/wizard/mrp_product_produce_views.xml2
-rw-r--r--addons/point_of_sale/models/pos_order.py9
-rw-r--r--addons/point_of_sale/static/src/js/chrome.js14
-rw-r--r--addons/point_of_sale/static/src/js/gui.js36
-rw-r--r--addons/point_of_sale/static/src/js/popups.js4
-rw-r--r--addons/point_of_sale/static/src/xml/pos.xml7
-rw-r--r--addons/point_of_sale/views/pos_order_report_view.xml2
-rw-r--r--addons/product/views/product_views.xml2
-rw-r--r--addons/purchase/models/purchase.py1
-rw-r--r--addons/report/models/ir_actions_report_xml.py2
-rw-r--r--addons/report_intrastat/views/report_intrastat_views.xml8
-rw-r--r--addons/sale/models/sale.py6
-rw-r--r--addons/sales_team/static/src/xml/sales_team_dashboard.xml2
-rw-r--r--addons/stock/models/stock_pack_operation.py8
-rw-r--r--addons/stock/tests/test_stock_flow.py6
-rw-r--r--addons/stock/views/product_views.xml3
-rw-r--r--addons/stock/views/stock_production_lot_views.xml1
-rw-r--r--addons/stock_account/report/stock_history.py2
-rw-r--r--addons/survey/models/survey.py2
-rw-r--r--addons/web/static/src/js/framework/session.js5
-rw-r--r--addons/web/static/src/js/views/form_relational_widgets.js8
-rw-r--r--addons/web/static/src/js/views/list_view.js2
-rw-r--r--addons/web/static/src/js/views/pivot_view.js1
-rw-r--r--addons/website_sale_digital/controllers/main.py10
-rw-r--r--addons/website_sale_digital/models/account_invoice.py8
-rw-r--r--addons/website_slides/models/slides.py30
41 files changed, 373 insertions, 108 deletions
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index 8ac7194e3d0..f114e2e889d 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -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')
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 0d2569593f1..1252aa647bf 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -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)
diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py
index 34c7e28cac1..75c09b3ac7e 100644
--- a/addons/account/models/account_payment.py
+++ b/addons/account/models/account_payment.py
@@ -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
diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml
index 5f83012ad07..532777a5066 100644
--- a/addons/account/views/account_view.xml
+++ b/addons/account/views/account_view.xml
@@ -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"/>
diff --git a/addons/crm/data/sales_config_settings_data.xml b/addons/crm/data/sales_config_settings_data.xml
index 9aef98293b6..be269cafec9 100644
--- a/addons/crm/data/sales_config_settings_data.xml
+++ b/addons/crm/data/sales_config_settings_data.xml
@@ -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>
diff --git a/addons/crm/views/crm_lead_views.xml b/addons/crm/views/crm_lead_views.xml
index d90664ca1c5..944212cde3e 100644
--- a/addons/crm/views/crm_lead_views.xml
+++ b/addons/crm/views/crm_lead_views.xml
@@ -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)]"/>
diff --git a/addons/im_livechat/static/src/less/im_livechat.less b/addons/im_livechat/static/src/less/im_livechat.less
index c57bfad230a..c52fc7d2b9e 100644
--- a/addons/im_livechat/static/src/less/im_livechat.less
+++ b/addons/im_livechat/static/src/less/im_livechat.less
@@ -16,6 +16,7 @@
border-bottom: 0px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
+ z-index: 5;
}
.o_chat_window {
diff --git a/addons/l10n_ro/data/account_fiscal_position_data.xml b/addons/l10n_ro/data/account_fiscal_position_data.xml
index 8649103fa8f..9ff61aa3f0e 100644
--- a/addons/l10n_ro/data/account_fiscal_position_data.xml
+++ b/addons/l10n_ro/data/account_fiscal_position_data.xml
@@ -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"/>
diff --git a/addons/l10n_ro/data/account_tax_data.xml b/addons/l10n_ro/data/account_tax_data.xml
index 8bba8727f27..373fea4c16c 100644
--- a/addons/l10n_ro/data/account_tax_data.xml
+++ b/addons/l10n_ro/data/account_tax_data.xml
@@ -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>
diff --git a/addons/maintenance/__manifest__.py b/addons/maintenance/__manifest__.py
index b30f3bd2d73..9bdde95f507 100644
--- a/addons/maintenance/__manifest__.py
+++ b/addons/maintenance/__manifest__.py
@@ -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,
diff --git a/addons/maintenance/models/maintenance.py b/addons/maintenance/models/maintenance.py
index 4d2efbdeaa0..87d6f6535f3 100644
--- a/addons/maintenance/models/maintenance.py
+++ b/addons/maintenance/models/maintenance.py
@@ -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.")
diff --git a/addons/maintenance/tests/test_maintenance.py b/addons/maintenance/tests/test_maintenance.py
index a9c55d95ffc..d73301d7ec2 100644
--- a/addons/maintenance/tests/test_maintenance.py
+++ b/addons/maintenance/tests/test_maintenance.py
@@ -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.')
diff --git a/addons/maintenance/views/maintenance_views.xml b/addons/maintenance/views/maintenance_views.xml
index 99858bff69f..6be82359009 100644
--- a/addons/maintenance/views/maintenance_views.xml
+++ b/addons/maintenance/views/maintenance_views.xml
@@ -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>
diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py
index c57c079be7a..1f772e445f4 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -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,
diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py
index 33ff2293052..42df10850ca 100644
--- a/addons/mrp/models/stock_move.py
+++ b/addons/mrp/models/stock_move.py
@@ -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')
diff --git a/addons/mrp/wizard/mrp_product_produce_views.xml b/addons/mrp/wizard/mrp_product_produce_views.xml
index 9a22d279766..6650449dd16 100644
--- a/addons/mrp/wizard/mrp_product_produce_views.xml
+++ b/addons/mrp/wizard/mrp_product_produce_views.xml
@@ -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>
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index 201ed22e66c..c2f9a0ca076 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -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,
diff --git a/addons/point_of_sale/static/src/js/chrome.js b/addons/point_of_sale/static/src/js/chrome.js
index f7bf1f78010..02d1123bcc3 100644
--- a/addons/point_of_sale/static/src/js/chrome.js
+++ b/addons/point_of_sale/static/src/js/chrome.js
@@ -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) {
diff --git a/addons/point_of_sale/static/src/js/gui.js b/addons/point_of_sale/static/src/js/gui.js
index f0755fa7573..87cb30caf78 100644
--- a/addons/point_of_sale/static/src/js/gui.js
+++ b/addons/point_of_sale/static/src/js/gui.js
@@ -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 ---- */
diff --git a/addons/point_of_sale/static/src/js/popups.js b/addons/point_of_sale/static/src/js/popups.js
index f4429840237..eaeae4d6f83 100644
--- a/addons/point_of_sale/static/src/js/popups.js
+++ b/addons/point_of_sale/static/src/js/popups.js
@@ -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(){
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index 3245c9a409b..3821fd212dc 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -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"/>
diff --git a/addons/point_of_sale/views/pos_order_report_view.xml b/addons/point_of_sale/views/pos_order_report_view.xml
index 9559833a92b..43700f170c2 100644
--- a/addons/point_of_sale/views/pos_order_report_view.xml
+++ b/addons/point_of_sale/views/pos_order_report_view.xml
@@ -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"/>
diff --git a/addons/product/views/product_views.xml b/addons/product/views/product_views.xml
index 3bbc078c6cf..7c245ff55dd 100644
--- a/addons/product/views/product_views.xml
+++ b/addons/product/views/product_views.xml
@@ -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"/>
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index c46557ee51f..36e428a7730 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -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
diff --git a/addons/report/models/ir_actions_report_xml.py b/addons/report/models/ir_actions_report_xml.py
index 667a2b43349..8461815e389 100644
--- a/addons/report/models/ir_actions_report_xml.py
+++ b/addons/report/models/ir_actions_report_xml.py
@@ -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')]
diff --git a/addons/report_intrastat/views/report_intrastat_views.xml b/addons/report_intrastat/views/report_intrastat_views.xml
index ca1c5c67c10..f91c5db3745 100644
--- a/addons/report_intrastat/views/report_intrastat_views.xml
+++ b/addons/report_intrastat/views/report_intrastat_views.xml
@@ -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>
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index 13d1e7a740a..a9d5c3dac20 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -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
diff --git a/addons/sales_team/static/src/xml/sales_team_dashboard.xml b/addons/sales_team/static/src/xml/sales_team_dashboard.xml
index 93f2f3ca9a9..b1bb4a6ca65 100644
--- a/addons/sales_team/static/src/xml/sales_team_dashboard.xml
+++ b/addons/sales_team/static/src/xml/sales_team_dashboard.xml
@@ -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>
diff --git a/addons/stock/models/stock_pack_operation.py b/addons/stock/models/stock_pack_operation.py
index b6c46310b94..6e51a573546 100644
--- a/addons/stock/models/stock_pack_operation.py
+++ b/addons/stock/models/stock_pack_operation.py
@@ -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
diff --git a/addons/stock/tests/test_stock_flow.py b/addons/stock/tests/test_stock_flow.py
index e0ddb97a84e..3ee20467a88 100644
--- a/addons/stock/tests/test_stock_flow.py
+++ b/addons/stock/tests/test_stock_flow.py
@@ -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()
diff --git a/addons/stock/views/product_views.xml b/addons/stock/views/product_views.xml
index f3c05e4dcb1..6fe6c489083 100644
--- a/addons/stock/views/product_views.xml
+++ b/addons/stock/views/product_views.xml
@@ -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')]"/>
diff --git a/addons/stock/views/stock_production_lot_views.xml b/addons/stock/views/stock_production_lot_views.xml
index 31a2c532a13..cfa5d3929ce 100644
--- a/addons/stock/views/stock_production_lot_views.xml
+++ b/addons/stock/views/stock_production_lot_views.xml
@@ -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"/>
diff --git a/addons/stock_account/report/stock_history.py b/addons/stock_account/report/stock_history.py
index 3e67dd1db8d..ccf150954dc 100644
--- a/addons/stock_account/report/stock_history.py
+++ b/addons/stock_account/report/stock_history.py
@@ -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
diff --git a/addons/survey/models/survey.py b/addons/survey/models/survey.py
index 99ce35e02a3..3d367b0364b 100644
--- a/addons/survey/models/survey.py
+++ b/addons/survey/models/survey.py
@@ -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):
diff --git a/addons/web/static/src/js/framework/session.js b/addons/web/static/src/js/framework/session.js
index 6af97721926..516436af93c 100644
--- a/addons/web/static/src/js/framework/session.js
+++ b/addons/web/static/src/js/framework/session.js
@@ -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()) {
diff --git a/addons/web/static/src/js/views/form_relational_widgets.js b/addons/web/static/src/js/views/form_relational_widgets.js
index 46136e21565..06bca6cd382 100644
--- a/addons/web/static/src/js/views/form_relational_widgets.js
+++ b/addons/web/static/src/js/views/form_relational_widgets.js
@@ -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();
diff --git a/addons/web/static/src/js/views/list_view.js b/addons/web/static/src/js/views/list_view.js
index 572fcbff95f..b02c91ed147 100644
--- a/addons/web/static/src/js/views/list_view.js
+++ b/addons/web/static/src/js/views/list_view.js
@@ -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;
}
diff --git a/addons/web/static/src/js/views/pivot_view.js b/addons/web/static/src/js/views/pivot_view.js
index 57c26311acc..95e8d13e5ff 100644
--- a/addons/web/static/src/js/views/pivot_view.js
+++ b/addons/web/static/src/js/views/pivot_view.js
@@ -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 = {};
diff --git a/addons/website_sale_digital/controllers/main.py b/addons/website_sale_digital/controllers/main.py
index 312dcca81f2..089f2b97f35 100644
--- a/addons/website_sale_digital/controllers/main.py
+++ b/addons/website_sale_digital/controllers/main.py
@@ -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',
)
diff --git a/addons/website_sale_digital/models/account_invoice.py b/addons/website_sale_digital/models/account_invoice.py
index b6b28026dde..798faca55a0 100644
--- a/addons/website_sale_digital/models/account_invoice.py
+++ b/addons/website_sale_digital/models/account_invoice.py
@@ -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'],
)
diff --git a/addons/website_slides/models/slides.py b/addons/website_slides/models/slides.py
index 977a39316b5..91211ea79f4 100644
--- a/addons/website_slides/models/slides.py
+++ b/addons/website_slides/models/slides.py
@@ -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']