[MERGE] foward port of 10.0 up to revision 84a650e33a

This commit is contained in:
qdp-odoo
2016-12-30 17:21:25 +01:00
parent 33a413eb29
commit 4f29b77e3d
103 changed files with 637 additions and 4563 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ TL;DR
* If you [make a pull request](https://github.com/odoo/odoo/wiki/Contributing#making-pull-requests),
do not create an issue! Use the PR description for that
* Issues are handled with a much lower priority than pull requests
* Use this [template](https://github.com/odoo/odoo/tree/9.0/.github/ISSUE_TEMPLATE.md)
* Use this [template](https://github.com/odoo/odoo/tree/10.0/.github/ISSUE_TEMPLATE.md)
when reporting issues. Please search for duplicates first!
* Pull requests must be made against the [correct version](https://github.com/odoo/odoo/wiki/Contributing#against-which-version-should-i-submit-a-patch)
* There are restrictions on the kind of [changes allowed in stable series](https://github.com/odoo/odoo/wiki/Contributing#what-does-stable-mean)
+6
View File
@@ -6728,6 +6728,12 @@ msgstr ""
msgid "Please create some invoice lines."
msgstr ""
#. module: account
#: code:addons/account/models/account_move.py:140
#, python-format
msgid "Please define a sequence for the refunds"
msgstr ""
#. module: account
#: code:addons/account/models/account_move.py:141
#, python-format
+1 -1
View File
@@ -125,7 +125,7 @@ class AccountAccount(models.Model):
@api.returns('self', lambda value: value.id)
def copy(self, default=None):
default = dict(default or {})
default.update(code=_("%s (copy)") % (self.code or ''))
default.setdefault('code', _("%s (copy)") % (self.code or ''))
return super(AccountAccount, self).copy(default)
@api.multi
+1 -1
View File
@@ -1124,7 +1124,7 @@ class AccountInvoice(models.Model):
res.setdefault(line.tax_id.tax_group_id, 0.0)
res[line.tax_id.tax_group_id] += line.amount
res = sorted(res.items(), key=lambda l: l[0].sequence)
res = map(lambda l: (l[0].name, formatLang(self.env, l[1], currency_obj=currency)), res)
res = map(lambda l: (l[0].name, l[1]), res)
return res
+5 -1
View File
@@ -133,7 +133,6 @@ class AccountMove(models.Model):
def post(self):
invoice = self._context.get('invoice', False)
self._post_validate()
for move in self:
move.line_ids.create_analytic_lines()
if move.name == '/':
@@ -147,7 +146,10 @@ class AccountMove(models.Model):
# If invoice is actually refund and journal has a refund_sequence then use that one or use the regular one
sequence = journal.sequence_id
if invoice and invoice.type in ['out_refund', 'in_refund'] and journal.refund_sequence:
if not journal.refund_sequence_id:
raise UserError(_('Please define a sequence for the refunds'))
sequence = journal.refund_sequence_id
new_name = sequence.with_context(ir_sequence_date=move.date).next_by_id()
else:
raise UserError(_('Please define a sequence on the journal.'))
@@ -1100,6 +1102,7 @@ class AccountMoveLine(models.Model):
# Create tax lines
for tax_vals in res['taxes']:
if tax_vals['amount']:
tax = self.env['account.tax'].browse([tax_vals['id']])
account_id = (amount > 0 and tax_vals['account_id'] or tax_vals['refund_account_id'])
if not account_id: account_id = vals['account_id']
temp = {
@@ -1111,6 +1114,7 @@ class AccountMoveLine(models.Model):
'statement_id': vals.get('statement_id'),
'debit': tax_vals['amount'] > 0 and tax_vals['amount'] or 0.0,
'credit': tax_vals['amount'] < 0 and -tax_vals['amount'] or 0.0,
'analytic_account_id': vals.get('analytic_account_id') if tax.analytic else False,
}
bank = self.env["account.bank.statement"].browse(vals.get('statement_id'))
if bank.currency_id != bank.company_id.currency_id:
+9 -1
View File
@@ -396,7 +396,15 @@ class account_payment(models.Model):
#Reconcile with the invoices
if self.payment_difference_handling == 'reconcile' and self.payment_difference:
writeoff_line = self._get_shared_move_line_vals(0, 0, 0, move.id, False)
debit_wo, credit_wo, amount_currency_wo, currency_id = aml_obj.with_context(date=self.payment_date).compute_amount_fields(self.payment_difference, self.currency_id, self.company_id.currency_id, invoice_currency)
amount_currency_wo, currency_id = aml_obj.with_context(date=self.payment_date).compute_amount_fields(self.payment_difference, self.currency_id, self.company_id.currency_id, invoice_currency)[2:]
# 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 = 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)
amount_wo = total_residual_company_signed - total_payment_company_signed
debit_wo = amount_wo > 0 and amount_wo or 0.0
credit_wo = amount_wo < 0 and -amount_wo or 0.0
writeoff_line['name'] = _('Counterpart')
writeoff_line['account_id'] = self.writeoff_account_id.id
writeoff_line['debit'] = debit_wo
+1 -1
View File
@@ -20,7 +20,7 @@ class AccountFiscalPosition(models.Model):
company_id = fields.Many2one('res.company', string='Company')
account_ids = fields.One2many('account.fiscal.position.account', 'position_id', string='Account Mapping', copy=True)
tax_ids = fields.One2many('account.fiscal.position.tax', 'position_id', string='Tax Mapping', copy=True)
note = fields.Text('Notes', help="Legal mentions that have to be printed on the invoices.")
note = fields.Text('Notes', translate=True, help="Legal mentions that have to be printed on the invoices.")
auto_apply = fields.Boolean(string='Detect Automatically', help="Apply automatically this fiscal position.")
vat_required = fields.Boolean(string='VAT required', help="Apply only if partner has a VAT number.")
country_id = fields.Many2one('res.country', string='Country',
@@ -33,10 +33,10 @@ class ReportOverdue(models.AbstractModel):
@api.model
def render_html(self, docids, data=None):
totals = {}
lines = self._get_account_move_lines(self.ids)
lines = self._get_account_move_lines(docids)
lines_to_display = {}
company_currency = self.env.user.company_id.currency_id
for partner_id in self.ids:
for partner_id in docids:
lines_to_display[partner_id] = {}
totals[partner_id] = {}
for line_tmp in lines[partner_id]:
@@ -58,9 +58,9 @@ class ReportOverdue(models.AbstractModel):
totals[partner_id][currency]['mat'] += line['mat']
totals[partner_id][currency]['total'] += line['debit'] - line['credit']
docargs = {
'doc_ids': self.ids,
'doc_ids': docids,
'doc_model': 'res.partner',
'docs': self.env['res.partner'].browse(self.ids),
'docs': self.env['res.partner'].browse(docids),
'time': time,
'Lines': lines_to_display,
'Totals': totals,
+24 -3
View File
@@ -102,9 +102,9 @@
</tr>
<t t-foreach="o._get_tax_amount_by_group()" t-as="amount_by_group">
<tr>
<td><span t-esc="amount_by_group[0]"/></td>
<td><span t-esc="amount_by_group[0] if len(o.tax_line_ids) > 1 else (o.tax_line_ids.tax_id.description or o.tax_line_ids.tax_id.name)"/></td>
<td class="text-right">
<span t-esc="amount_by_group[1]"/>
<span t-esc="amount_by_group[1]" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
</td>
</tr>
</t>
@@ -117,7 +117,28 @@
</table>
</div>
</div>
<div class="row" t-if="len(o.tax_line_ids) > 1">
<div class="col-xs-6">
<table class="table table-condensed">
<thead>
<tr>
<th>Tax</th>
<th class="text-right">Amount</th>
</tr>
</thead>
<tbody>
<tr t-foreach="o.tax_line_ids" t-as="t">
<td><span t-field="t.tax_id.description"/></td>
<td class="text-right">
<span t-field="t.amount" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<p t-if="o.comment">
<strong>Comment:</strong>
<span t-field="o.comment"/>
@@ -75,7 +75,7 @@ class AccountInvoiceRefund(models.TransientModel):
for tmpline in refund.move_id.line_ids:
if tmpline.account_id.id == inv.account_id.id:
to_reconcile_lines += tmpline
to_reconcile_lines.reconcile()
to_reconcile_lines.filtered(lambda l: l.reconciled == False).reconcile()
if mode == 'modify':
invoice = inv.read(
['name', 'type', 'number', 'reference',
@@ -65,7 +65,10 @@ class AccountInvoiceLine(models.Model):
return res
def _set_additional_fields(self, invoice):
rec = self.env['account.analytic.default'].account_get(self.product_id.id, self.invoice_id.partner_id.id, self.env.uid,
fields.Date.today(), company_id=self.company_id.id)
self.account_analytic_id = rec.analytic_id.id
if not self.account_analytic_id:
rec = self.env['account.analytic.default'].account_get(
self.product_id.id, self.invoice_id.partner_id.id, self.env.uid,
fields.Date.today(), company_id=self.company_id.id)
if rec:
self.account_analytic_id = rec.analytic_id.id
super(AccountInvoiceLine, self)._set_additional_fields(invoice)
@@ -1,8 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo import api, fields, models, tools
class AssetAssetReport(models.Model):
_name = "asset.asset.report"
@@ -28,6 +27,7 @@ class AssetAssetReport(models.Model):
@api.model_cr
def init(self):
tools.drop_view_if_exists(self._cr, 'asset_asset_report')
self._cr.execute("""
create or replace view asset_asset_report as (
select
@@ -40,6 +40,7 @@ class ReportAssertAccount(models.AbstractModel):
'reconciled_inv': reconciled_inv, # specific function used in different tests
'result': None, # used to store the result of the test
'column_order': None, # used to choose the display order of columns (in case you are returning a list of dict)
'_': _,
}
safe_eval(code_exec, localdict, mode="exec", nocopy=True)
result = localdict['result']
+24 -24
View File
@@ -301,30 +301,30 @@ class IrModelFieldsAnonymizeWizard(models.TransientModel):
else:
raise Exception("Unknown query type '%s'. Valid types are: sql, python." % (query['query_type'], ))
# update the anonymization fields:
ano_fields = IrModelFieldsAnonymization.search([('state', '!=', 'not_existing')])
ano_fields.write({'state': 'clear'})
# update the anonymization fields:
ano_fields = IrModelFieldsAnonymization.search([('state', '!=', 'not_existing')])
ano_fields.write({'state': 'clear'})
# add a result message in the wizard:
self.msg = '\n'.join(["Successfully reversed the anonymization.", ""])
# add a result message in the wizard:
self.msg = '\n'.join(["Successfully reversed the anonymization.", ""])
# create a new history record:
history = self.env['ir.model.fields.anonymization.history'].create({
'date': fields.Datetime.now(),
'field_ids': [[6, 0, ano_fields.ids]],
'msg': self.msg,
'filepath': False,
'direction': 'anonymized -> clear',
'state': 'done'
})
# create a new history record:
history = self.env['ir.model.fields.anonymization.history'].create({
'date': fields.Datetime.now(),
'field_ids': [[6, 0, ano_fields.ids]],
'msg': self.msg,
'filepath': False,
'direction': 'anonymized -> clear',
'state': 'done'
})
return {
'res_id': self.id,
'view_id': self.env.ref('anonymization.view_ir_model_fields_anonymize_wizard_form').ids,
'view_type': 'form',
"view_mode": 'form',
'res_model': 'ir.model.fields.anonymize.wizard',
'type': 'ir.actions.act_window',
'context': {'step': 'just_desanonymized'},
'target': 'new'
}
return {
'res_id': self.id,
'view_id': self.env.ref('anonymization.view_ir_model_fields_anonymize_wizard_form').ids,
'view_type': 'form',
"view_mode": 'form',
'res_model': 'ir.model.fields.anonymize.wizard',
'type': 'ir.actions.act_window',
'context': {'step': 'just_desanonymized'},
'target': 'new'
}
+1 -1
View File
@@ -1079,7 +1079,7 @@ class Meeting(models.Model):
if self.month_by == 'date' and (self.day < 1 or self.day > 31):
raise UserError(_("Please select a proper day of the month."))
if self.month_by == 'day': # Eg : Second Monday of the month
if self.month_by == 'day' and self.byday and self.week_list: # Eg : Second Monday of the month
return ';BYDAY=' + self.byday + self.week_list
elif self.month_by == 'date': # Eg : 16th of the month
return ';BYMONTHDAY=' + str(self.day)
File diff suppressed because it is too large Load Diff
+19 -17
View File
@@ -2,6 +2,8 @@ odoo.define('planner_crm.planner', function (require) {
"use strict";
var planner = require('web.planner.common');
var core = require('web.core');
var _t = core._t;
planner.PlannerDialog.include({
prepare_planner_event: function () {
@@ -9,26 +11,26 @@ planner.PlannerDialog.include({
if (this.planner['planner_application'] === 'planner_crm') {
var stages = {
'solution_selling': [
'Territory', 'Qualified', 'Qualified Sponsor',
'Proposal', 'Negotiation', 'Won', '',
'New propspect assigned to the right salesperson',
'Set fields: Expected Revenue, Expected Closing Date, Next Action',
'You are in discussion with the decision maker and HE agreed on his pain points',
'Quotation sent to customer', 'The customer came back to you to discuss your quotation',
'Quotation signed by the customer', ''],
_t('Territory'), _t('Qualified'), _t('Qualified Sponsor'),
_t('Proposal'), _t('Negotiation'), _t('Won'), '',
_t('New propspect assigned to the right salesperson'),
_t('Set fields: Expected Revenue, Expected Closing Date, Next Action'),
_t('You are in discussion with the decision maker and HE agreed on his pain points'),
_t('Quotation sent to customer'), _t('The customer came back to you to discuss your quotation'),
_t('Quotation signed by the customer'), ''],
'b2c': [
'New', 'Initial Contact', 'Product Demonstration', 'Proposal', 'Won', '', '',
'', 'Phone call with following questions: ...',
'Meeting with a demo. Set Fields: expected revenue, closing date',
'Quotation sent', '', '', ''],
_t('New'), _t('Initial Contact'), _t('Product Demonstration'), _t('Proposal'), _t('Won'), '', '',
'', _t('Phone call with following questions: ...'),
_t('Meeting with a demo. Set Fields: expected revenue, closing date'),
_t('Quotation sent'), '', '', ''],
'b2b': [
'New', 'Qualified', 'Needs assessment', 'POC Sold', 'Demonstration', 'Proposal', 'Won',
'Set fields: Expected Revenue, Expected Closing Date, Next Action',
'Close opportunity if: "pre-sales days * $500" < "expected revenue" * probability',
'GAP analysis with customer', 'Create a Proof of Concept with consultants',
'POC demonstration to the customer', 'Final Proposal sent', ''],
_t('New'), _t('Qualified'), _t('Needs assessment'), _t('POC Sold'), _t('Demonstration'), _t('Proposal'), _t('Won'),
_t('Set fields: Expected Revenue, Expected Closing Date, Next Action'),
_t('Close opportunity if: "pre-sales days * $500" < "expected revenue" * probability'),
_t('GAP analysis with customer'), _t('Create a Proof of Concept with consultants'),
_t('POC demonstration to the customer'), _t('Final Proposal sent'), ''],
'odoo_default': [
'New', 'Qualified', 'Proposition', 'Negotiation', 'Won', 'Lost', '',
_t('New'), _t('Qualified'), _t('Proposition'), _t('Negotiation'), _t('Won'), _t('Lost'), '',
'', '', '', '', '', '', '']
};
this.$el.on('change', '#input_element_pipeline', function(ev) {
+1 -1
View File
@@ -244,7 +244,7 @@
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
<separator/>
<filter string="Unconfirmed" name="draft" domain="[('state','=','draft')]" help="Events in New state"/>
<filter string="Confirmed" domain="[('state','=','confirm')]" help="Confirmed events"/>
<filter name="confirm" string="Confirmed" domain="[('state','=','confirm')]" help="Confirmed events"/>
<separator/>
<filter string="My Events" help="My Events" domain="[('user_id','=',uid)]"/>
<separator/>
+1 -1
View File
@@ -61,4 +61,4 @@ class SaleOrderLine(models.Model):
@api.onchange('event_ticket_id')
def _onchange_event_ticket_id(self):
self.price_unit = self.event_ticket_id.price
self.price_unit = (self.event_id.company_id or self.env.user.company_id).currency_id.compute(self.event_ticket_id.price, self.order_id.currency_id)
-5
View File
@@ -505,11 +505,6 @@ class Challenge(models.Model):
goals = Goals.search(domain, order="completeness desc, current desc")
for ranking, goal in enumerate(goals):
# bail immediately if any goal of the challenge hasn't been
# reached
if goal.state != 'reached':
return []
if user and goal.user_id == user:
line_data['own_goal_id'] = goal.id
elif restrict_top and ranking > restrict_top:
+5
View File
@@ -229,6 +229,11 @@ class Employee(models.Model):
user_field_lst.append(name)
return user_field_lst
@api.multi
def _message_auto_subscribe_notify(self, partner_ids):
# Do not notify user it has been marked as follower of its employee.
return
class Department(models.Model):
+1
View File
@@ -132,6 +132,7 @@ class HrExpense(models.Model):
'amount_currency': line['price'] > 0 and abs(line.get('amount_currency')) or - abs(line.get('amount_currency')),
'currency_id': line.get('currency_id'),
'tax_line_id': line.get('tax_line_id'),
'tax_ids': line.get('tax_ids'),
'quantity': line.get('quantity', 1.00),
'product_id': line.get('product_id'),
'product_uom_id': line.get('uom_id'),
+2 -18
View File
@@ -348,9 +348,9 @@ class Holidays(models.Model):
employee_id = values.get('employee_id', False)
if not self._check_state_access_right(values):
raise AccessError(_('You cannot set a leave request as \'%s\'. Contact a human resource manager.') % values.get('state'))
if not values.get('department_id'):
values.update({'department_id': self.env['hr.employee'].browse(employee_id).department_id.id})
holiday = super(Holidays, self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True)).create(values)
if values.get('state') == 'confirm':
self.subscribe_followers()
holiday.add_follower(employee_id)
return holiday
@@ -360,8 +360,6 @@ class Holidays(models.Model):
if not self._check_state_access_right(values):
raise AccessError(_('You cannot set a leave request as \'%s\'. Contact a human resource manager.') % values.get('state'))
result = super(Holidays, self).write(values)
if values.get('state') == 'confirm':
self.subscribe_followers()
self.add_follower(employee_id)
return result
@@ -375,20 +373,6 @@ class Holidays(models.Model):
# Business methods
####################################################
@api.multi
def subscribe_followers(self):
for holiday in self:
if holiday.department_id:
# Subscribe the followers of the department following the `confirmed` subtype
# It's done manually because `message_auto_subscribe` only works on fields for which
# the value is passed to the `create` or `write` methods,
# it doesn't work for related/computed fields
# This can be removed as soon as `department_id` on `hr.holidays` becomes a regular
# fields or as soon as `message_auto_subscribe` works with related/computed fields.
confirmed_subtype = self.env.ref('hr_holidays.mt_department_holidays_confirmed')
partner_ids = [follower.partner_id.id for follower in holiday.department_id.message_follower_ids if confirmed_subtype in follower.subtype_ids]
holiday.message_subscribe(partner_ids)
@api.multi
def _create_resource_leave(self):
""" This method will create entry in resource calendar leave object at the time of holidays validated """
@@ -58,8 +58,12 @@ class HrHolidaySummaryReport(models.AbstractModel):
('date_to', '>=', str(start_date))
])
for holiday in holidays:
date_from = fields.Date.from_string(holiday.date_from)
date_to = fields.Date.from_string(holiday.date_to)
# Convert date to user timezone, otherwise the report will not be consistent with the
# value displayed in the interface.
date_from = fields.Datetime.from_string(holiday.date_from)
date_from = fields.Datetime.context_timestamp(holiday, date_from).date()
date_to = fields.Datetime.from_string(holiday.date_to)
date_to = fields.Datetime.context_timestamp(holiday, date_to).date()
for index in range(0, ((date_to - date_from).days + 1)):
if date_from >= start_date and date_from <= end_date:
res[(date_from-start_date).days]['color'] = holiday.holiday_status_id.color_name
+3 -13
View File
@@ -1,25 +1,15 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
# Copyright (c) 2009-2010 Salvatore Josué Trimarchi Pinto <salvatore@trigluu.com>
# (http://trigluu.com)
# Copyright (c) 2009-2016 Salvatore Josué Trimarchi Pinto <trimarchi@bacgroup.net>
#
# This module provides a minimal Honduran chart of accounts that can be use
# to build upon a more complex one. It also includes a chart of taxes and
# the Lempira currency.
#
# This module is based on the Guatemalan chart of accounts:
# Copyright (c) 2009-2010 Soluciones Tecnologócias Prisma S.A. All Rights Reserved.
# José Rodrigo Fernández Menegazzo, Soluciones Tecnologócias Prisma S.A.
# (http://www.solucionesprisma.com)
#
# This module works with OpenERP 6.0 to 8.0
#
{
'name': 'Honduras - Accounting',
'version': '0.1',
'version': '0.2',
'category': 'Localization',
'description': """
This is the base module to manage the accounting chart for Honduras.
@@ -29,7 +19,7 @@ Agrega una nomenclatura contable para Honduras. También incluye impuestos y la
moneda Lempira. -- Adds accounting chart for Honduras. It also includes taxes
and the Lempira currency.""",
'author': 'Salvatore Josue Trimarchi Pinto',
'website': 'http://trigluu.com',
'website': 'http://bacgroup.net',
'depends': ['base', 'account'],
'data': [
'data/l10n_hn_chart_data.xml',
+1
View File
@@ -37,6 +37,7 @@ With this module you will have:
"data": [
"data/account_tag_data.xml",
"data/l10n_mx_chart_data.xml",
"data/account_data.xml",
"data/account_tax_data.xml",
"data/account_chart_template_data.yml",
],
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Account Tax Group -->
<record id="tax_group_iva_ret" model="account.tax.group">
<field name="name">IVA-RET</field>
</record>
<record id="tax_group_iva" model="account.tax.group">
<field name="name">IVA</field>
</record>
<record id="tax_group_iva_exent" model="account.tax.group">
<field name="name">IVA-EXENTO</field>
</record>
<record id="tax_group_isr" model="account.tax.group">
<field name="name">ISR</field>
</record>
<record id="tax_group_isr_ret" model="account.tax.group">
<field name="name">ISR-RET</field>
</record>
<record id="tax_group_ieps" model="account.tax.group">
<field name="name">IEPS</field>
</record>
<record id="tax_group_ietu" model="account.tax.group">
<field name="name">IETU</field>
</record>
</data>
</odoo>
+12 -2
View File
@@ -46,25 +46,27 @@
<record id="tax9" model="account.tax.template">
<field name="chart_template_id" ref="mx_coa"/>
<field name="name">IVA(0%) VENTAS</field>
<field name="description">ITAX_010-IN</field>
<field name="description">IVA</field>
<field name="amount">0</field>
<field name="amount_type">percent</field>
<field name="type_tax_use">sale</field>
<field name="account_id" ref="cuenta209_01"/>
<field name="refund_account_id" ref="cuenta209_01"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_01')])]"/>
<field name="tax_group_id" ref="tax_group_iva"/>
</record>
<record id="tax12" model="account.tax.template">
<field name="chart_template_id" ref="mx_coa"/>
<field name="name">IVA(16%) VENTAS</field>
<field name="description">ITAX_160-IN</field>
<field name="description">IVA</field>
<field name="amount">16</field>
<field name="amount_type">percent</field>
<field name="type_tax_use">sale</field>
<field name="account_id" ref="cuenta209_01"/>
<field name="refund_account_id" ref="cuenta209_01"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_03')])]"/>
<field name="tax_group_id" ref="tax_group_iva"/>
</record>
<record id="tax1" model="account.tax.template">
@@ -77,6 +79,7 @@
<field name="account_id" ref="cuenta216_10"/>
<field name="refund_account_id" ref="cuenta216_10"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_04')])]"/>
<field name="tax_group_id" ref="tax_group_iva_ret"/>
</record>
<record id="tax2" model="account.tax.template">
@@ -89,6 +92,7 @@
<field name="account_id" ref="cuenta216_10"/>
<field name="refund_account_id" ref="cuenta216_10"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_05')])]"/>
<field name="tax_group_id" ref="tax_group_iva_ret"/>
</record>
<record id="tax3" model="account.tax.template">
@@ -101,6 +105,7 @@
<field name="account_id" ref="cuenta216_03"/>
<field name="refund_account_id" ref="cuenta216_03"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_06')])]"/>
<field name="tax_group_id" ref="tax_group_isr_ret"/>
</record>
<record id="tax5" model="account.tax.template">
@@ -113,6 +118,7 @@
<field name="account_id" ref="cuenta216_04"/>
<field name="refund_account_id" ref="cuenta216_04"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_07')])]"/>
<field name="tax_group_id" ref="tax_group_isr_ret"/>
</record>
<record id="tax7" model="account.tax.template">
@@ -125,6 +131,7 @@
<field name="account_id" ref="cuenta216_10"/>
<field name="refund_account_id" ref="cuenta216_10"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_08')])]"/>
<field name="tax_group_id" ref="tax_group_iva_ret"/>
</record>
<record id="tax8" model="account.tax.template">
@@ -137,6 +144,7 @@
<field name="account_id" ref="cuenta216_10"/>
<field name="refund_account_id" ref="cuenta216_10"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_09')])]"/>
<field name="tax_group_id" ref="tax_group_iva_ret"/>
</record>
<record id="tax13" model="account.tax.template">
@@ -149,6 +157,7 @@
<field name="account_id" ref="cuenta119_01"/>
<field name="refund_account_id" ref="cuenta119_01"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_10')])]"/>
<field name="tax_group_id" ref="tax_group_iva"/>
</record>
<record id="tax14" model="account.tax.template">
@@ -161,6 +170,7 @@
<field name="account_id" ref="cuenta119_01"/>
<field name="refund_account_id" ref="cuenta119_01"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_12')])]"/>
<field name="tax_group_id" ref="tax_group_iva"/>
</record>
</data>
</odoo>
+9 -2
View File
@@ -154,6 +154,13 @@
<field name="reconcile" eval="True"/>
<field name="tag_ids" eval="[(6,0,[ref('account_tag_206_02')])]"/>
</record>
<record id='cuenta208_01' model='account.account.template'>
<field name='name'>IVA trasladado cobrado</field>
<field name='code'>208.01.01</field>
<field name="chart_template_id" ref="mx_coa"/>
<field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
<field name="tag_ids" eval="[(6,0,[ref('account_tag_208_01')])]"/>
</record>
<record id='cuenta209_01' model='account.account.template'>
<field name='name'>IVA trasladado no cobrado</field>
<field name='code'>209.01.01</field>
@@ -177,12 +184,12 @@
</record>
<record id='cuenta216_10' model='account.account.template'>
<field name='name'>Impuestos retenidos de IVA</field>
<field name='code'>216.10.01</field>
<field name='code'>216.10.10</field>
<field name="chart_template_id" ref="mx_coa"/>
<field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
<field name="tag_ids" eval="[(6,0,[ref('account_tag_216_10')])]"/>
</record>
<record id='cuenta216_13' model='account.account.template'>
<record id='cuenta216_10_20' model='account.account.template'>
<field name='name'>Impuestos retenidos de iva efectivamente pagados</field>
<field name='code'>216.10.20</field>
<field name="chart_template_id" ref="mx_coa"/>
+8 -10
View File
@@ -29,12 +29,11 @@ class AccountChartTemplate(models.Model):
account_tax_obj = self.env['account.tax']
account_obj = self.env['account.account']
taxes_acc = {
'ITAX_010-IN': account_obj.search([('code', '=', '208.01.01')]),
'ITAX_160-IN': account_obj.search([('code', '=', '208.01.01')]),
'ITAXR_04-OUT': account_obj.search([('code', '=', '216.13.01')]),
'ITAXR_10-OUT': account_obj.search([('code', '=', '216.13.01')]),
'ITAX_1067-OUT': account_obj.search([('code', '=', '216.13.01')]),
'ITAX_167-OUT': account_obj.search([('code', '=', '216.13.01')]),
'IVA': account_obj.search([('code', '=', '208.01.01')]),
'ITAXR_04-OUT': account_obj.search([('code', '=', '216.10.20')]),
'ITAXR_10-OUT': account_obj.search([('code', '=', '216.10.20')]),
'ITAX_1067-OUT': account_obj.search([('code', '=', '216.10.20')]),
'ITAX_167-OUT': account_obj.search([('code', '=', '216.10.20')]),
'ITAX_010-OUT': account_obj.search([('code', '=', '208.01.01')]),
'ITAX_160-OUT': account_obj.search([('code', '=', '208.01.01')])}
@@ -67,15 +66,14 @@ class AccountChartTemplate(models.Model):
acc_template_ref, company, journals_dict=journals_dict)
if not self == self.env.ref('l10n_mx.mx_coa'):
return res
account = self.env.ref('l10n_mx.1_cuenta118_01')
res.append({
'type': 'general',
'name': _('Effectively Paid'),
'code': 'CBMX',
'company_id': company.id,
'default_credit_account_id': acc_template_ref.get(
self.income_currency_exchange_account_id.id),
'default_debit_account_id': acc_template_ref.get(
self.expense_currency_exchange_account_id.id),
'default_credit_account_id': account.id,
'default_debit_account_id': account.id,
'show_on_dashboard': True,
})
return res
+4 -4
View File
@@ -811,15 +811,15 @@
<field name="chart_template_id" ref="vn_template"/>
<field name="name">Mệnh giá trái phiếu</field>
</record>
<record id="chart343112" model="account.account.template">
<field name="code">343112</field>
<record id="chart34312" model="account.account.template">
<field name="code">34312</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
<field name="chart_template_id" ref="vn_template"/>
<field name="name">Chiết khấu trái phiếu</field>
</record>
<record id="chart343113" model="account.account.template">
<field name="code">343113</field>
<record id="chart34313" model="account.account.template">
<field name="code">34313</field>
<field name="reconcile" eval="False"/>
<field name="user_type_id" ref="account.data_account_type_current_liabilities"/>
<field name="chart_template_id" ref="vn_template"/>
+1 -26
View File
@@ -1,10 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import cgi
import re
from odoo import api, fields, models
from odoo import fields, models
class MailShortcode(models.Model):
@@ -23,25 +20,3 @@ class MailShortcode(models.Model):
shortcode_type = fields.Selection([('image', 'Smiley'), ('text', 'Canned Response')], required=True, default='text',
help="* Smiley are only used for HTML code to display an image "\
"* Text (default value) is used to substitute text with another text")
@api.model
def create(self, values):
if values.get('substitution'):
values['substitution'] = self._sanitize_shorcode(values['substitution'])
return super(MailShortcode, self).create(values)
@api.multi
def write(self, values):
if values.get('substitution'):
values['substitution'] = self._sanitize_shorcode(values['substitution'])
return super(MailShortcode, self).write(values)
def _sanitize_shorcode(self, substitution):
""" Sanitize the shortcode substitution :
- HTML substitution : only allow the img tag (emoji)
- escape other substitutions to avoid XSS
"""
is_img_tag = re.match(r'''^<img\s+src=('|")([^'"]*)\1\s*/?>$''', substitution, re.M | re.I)
if is_img_tag:
return substitution
return cgi.escape(substitution)
+7
View File
@@ -2049,6 +2049,13 @@ class MailThread(models.AbstractModel):
if self.env.context.get('mail_auto_subscribe_no_notify'):
return
# send the email only to the current record and not all the ids matching active_domain !
# by default, send_mail for mass_mail use the active_domain instead of active_ids.
if 'active_domain' in self.env.context:
ctx = dict(self.env.context)
ctx.pop('active_domain')
self = self.with_context(ctx)
for record in self:
record.message_post_with_view(
'mail.message_user_assigned',
+1 -1
View File
@@ -668,7 +668,7 @@ var chat_manager = {
// to linkify the urls we end up with double linkification a bit everywhere.
// Ideally we want to keep the content as text internally and only make html
// enrichment at display time but the current design makes this quite hard to do.
var body = utils.linkify(_.str.trim(data.content));
var body = utils.parse_and_transform(_.str.trim(data.content), utils.add_link);
var msg = {
partner_ids: data.partner_ids,
+6 -7
View File
@@ -12,18 +12,17 @@ from odoo.tools.safe_eval import safe_eval
EXPRESSION_PATTERN = re.compile('(\$\{.+?\})')
def _reopen(self, res_id, model):
def _reopen(self, res_id, model, context=None):
# save original model in context, because selecting the list of available
# templates requires a model in context
context = dict(context or {}, default_model=model)
return {'type': 'ir.actions.act_window',
'view_mode': 'form',
'view_type': 'form',
'res_id': res_id,
'res_model': self._name,
'target': 'new',
# save original model in context, because selecting the list of available
# templates requires a model in context
'context': {
'default_model': model,
},
'context': context,
}
@@ -409,7 +408,7 @@ class MailComposer(models.TransientModel):
# generate the saved template
record.write({'template_id': template.id})
record.onchange_template_id_wrapper()
return _reopen(self, record.id, record.model)
return _reopen(self, record.id, record.model, context=self._context)
#------------------------------------------------------
# Template rendering
+1 -1
View File
@@ -129,7 +129,7 @@ class MrpUnbuild(models.Model):
produce_move.quantity_done = produce_move.product_uom_qty
produce_moves.move_validate()
produced_quant_ids = produce_moves.mapped('quant_ids').filtered(lambda quant: quant.qty > 0)
consume_move.quant_ids.write({'produced_quant_ids': [(6, 0, produced_quant_ids.ids)]})
consume_move.quant_ids.sudo().write({'produced_quant_ids': [(6, 0, produced_quant_ids.ids)]})
self.write({'state': 'done'})
+11 -2
View File
@@ -80,12 +80,21 @@ class MrpWorkcenter(models.Model):
@api.depends('time_ids', 'time_ids.date_end', 'time_ids.loss_type')
def _compute_working_state(self):
for workcenter in self:
time_log = self.env['mrp.workcenter.productivity'].search([('workcenter_id', '=', workcenter.id)], limit=1)
if not time_log or time_log.date_end:
# We search for a productivity line associated to this workcenter having no `date_end`.
# If we do not find one, the workcenter is not currently being used. If we find one, according
# to its `type_loss`, the workcenter is either being used or blocked.
time_log = self.env['mrp.workcenter.productivity'].search([
('workcenter_id', '=', workcenter.id),
('date_end', '=', False)
], limit=1)
if not time_log:
# the workcenter is not being used
workcenter.working_state = 'normal'
elif time_log.loss_type in ('productive', 'performance'):
# the productivity line has a `loss_type` that means the workcenter is being used
workcenter.working_state = 'done'
else:
# the workcenter is blocked
workcenter.working_state = 'blocked'
@api.multi
+2 -2
View File
@@ -118,10 +118,10 @@ class StockWarehouse(models.Model):
return routes
@api.multi
def _update_name_and_code(self, name, code):
def _update_name_and_code(self, name=False, code=False):
res = super(StockWarehouse, self)._update_name_and_code(name, code)
# change the manufacture procurement rule name
for warehouse in self:
if warehouse.manufacture_pull_id:
if warehouse.manufacture_pull_id and name:
warehouse.manufacture_pull_id.write({'name': warehouse.manufacture_pull_id.name.replace(warehouse.name, name, 1)})
return res
+1
View File
@@ -55,6 +55,7 @@ access_product_group_res_partner_mrp_manager,res_partner group_mrp_manager,base.
access_product_pricelist_item_mrp_manager,product.pricelist.item mrp_manager,product.model_product_pricelist_item,mrp.group_mrp_manager,1,1,1,1
access_resource_calendar_manufacturinguser,resource.calendar manufacturing.user,resource.model_resource_calendar,mrp.group_mrp_user,1,0,0,0
access_mrp_unbuild,mrp.unbuild,model_mrp_unbuild,group_mrp_user,1,0,0,0
access_mrp_unbuild_manager,mrp.unbuild manager,model_mrp_unbuild,group_mrp_manager,1,1,1,1
access_mrp_message_mrp_user,mrp.message,model_mrp_message,group_mrp_user,1,0,0,0
access_stock_move_lots,stock.move.lots,model_stock_move_lots,group_mrp_user,1,1,1,0
access_stock_move_lots_manager,stock.move.lots,model_stock_move_lots,group_mrp_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
55 access_product_pricelist_item_mrp_manager product.pricelist.item mrp_manager product.model_product_pricelist_item mrp.group_mrp_manager 1 1 1 1
56 access_resource_calendar_manufacturinguser resource.calendar manufacturing.user resource.model_resource_calendar mrp.group_mrp_user 1 0 0 0
57 access_mrp_unbuild mrp.unbuild model_mrp_unbuild group_mrp_user 1 0 0 0
58 access_mrp_unbuild_manager mrp.unbuild manager model_mrp_unbuild group_mrp_manager 1 1 1 1
59 access_mrp_message_mrp_user mrp.message model_mrp_message group_mrp_user 1 0 0 0
60 access_stock_move_lots stock.move.lots model_stock_move_lots group_mrp_user 1 1 1 0
61 access_stock_move_lots_manager stock.move.lots model_stock_move_lots group_mrp_manager 1 1 1 1
+1 -1
View File
@@ -39,7 +39,7 @@
<field name="sequence" widget="handle"/>
<field name="code"/>
<field name="name"/>
<field name="resource_type"/>
<field name="resource_type" invisible="1"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="active" invisible="1"/>
</tree>
+2 -2
View File
@@ -31,7 +31,7 @@
<field name="res_model">product.template</field>
<field name="view_type">form</field>
<field name="view_mode">kanban,tree,form</field>
<field name="context">{"search_default_products": 1, 'default_type': 'product'}</field>
<field name="context">{"search_default_consumable": 1, 'default_type': 'product'}</field>
</record>
<menuitem id="menu_mrp_product_form"
name="Products"
@@ -76,4 +76,4 @@
</field>
</record>
</data>
</odoo>
</odoo>
@@ -1,105 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * payment_transfer
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 9.saas~10c\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-08-18 14:07+0000\n"
"PO-Revision-Date: 2016-08-18 14:07+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: payment_transfer
#: code:addons/payment_transfer/models/payment_acquirer.py:68
#, python-format
msgid "; multiple order found"
msgstr ""
#. module: payment_transfer
#: code:addons/payment_transfer/models/payment_acquirer.py:66
#, python-format
msgid "; no order found"
msgstr ""
#. module: payment_transfer
#: model:payment.acquirer,cancel_msg:payment_transfer.payment_acquirer_transfer
msgid "<span><i>Cancel,</i> Your payment has been cancelled.</span>"
msgstr ""
#. module: payment_transfer
#: model:payment.acquirer,done_msg:payment_transfer.payment_acquirer_transfer
msgid "<span><i>Done,</i> Your online payment has been successfully processed. Thank you for your order.</span>"
msgstr ""
#. module: payment_transfer
#: model:payment.acquirer,error_msg:payment_transfer.payment_acquirer_transfer
msgid "<span><i>Error,</i> Please be aware that an error occurred during the transaction. The order has been confirmed but won't be paid. Don't hesitate to contact us if you have any questions on the status of your order.</span>"
msgstr ""
#. module: payment_transfer
#: model:payment.acquirer,pending_msg:payment_transfer.payment_acquirer_transfer
msgid "<span><i>Pending,</i> Your online payment has been successfully processed. But your order is not validated yet.</span>"
msgstr ""
#. module: payment_transfer
#: code:addons/payment_transfer/models/payment_acquirer.py:30
#: model:payment.acquirer,post_msg:payment_transfer.payment_acquirer_transfer
#, python-format
msgid "Bank Account"
msgstr ""
#. module: payment_transfer
#: code:addons/payment_transfer/models/payment_acquirer.py:30
#, python-format
msgid "Bank Accounts"
msgstr ""
#. module: payment_transfer
#: model:payment.acquirer,post_msg:payment_transfer.payment_acquirer_transfer
msgid "Communication"
msgstr ""
#. module: payment_transfer
#: model:ir.model,name:payment_transfer.model_payment_acquirer
msgid "Payment Acquirer"
msgstr ""
#. module: payment_transfer
#: model:ir.model,name:payment_transfer.model_payment_transaction
msgid "Payment Transaction"
msgstr ""
#. module: payment_transfer
#: model:payment.acquirer,post_msg:payment_transfer.payment_acquirer_transfer
msgid "Please use the following transfer details"
msgstr ""
#. module: payment_transfer
#: model:payment.acquirer,post_msg:payment_transfer.payment_acquirer_transfer
msgid "Please use the order name as communication reference."
msgstr ""
#. module: payment_transfer
#: model:payment.acquirer,pre_msg:payment_transfer.payment_acquirer_transfer
msgid "Transfer information will be provided after choosing the payment mode."
msgstr ""
#. module: payment_transfer
#: code:addons/payment_transfer/models/payment_acquirer.py:19
#: model:payment.acquirer,name:payment_transfer.payment_acquirer_transfer
#, python-format
msgid "Wire Transfer"
msgstr ""
#. module: payment_transfer
#: code:addons/payment_transfer/models/payment_acquirer.py:64
#, python-format
msgid "received data for reference %s"
msgstr ""
+2 -2
View File
@@ -25,13 +25,13 @@ class TransferPaymentAcquirer(models.Model):
accounts = journals.mapped('bank_account_id').name_get()
bank_title = _('Bank Accounts') if len(accounts) > 1 else _('Bank Account')
bank_accounts = ''.join(['<ul>'] + ['<li>%s</li>' % name for id, name in accounts] + ['</ul>'])
post_msg = '''<div>
post_msg = _('''<div>
<h3>Please use the following transfer details</h3>
<h4>%(bank_title)s</h4>
%(bank_accounts)s
<h4>Communication</h4>
<p>Please use the order name as communication reference.</p>
</div>''' % {
</div>''') % {
'bank_title': bank_title,
'bank_accounts': bank_accounts,
}
+11 -43
View File
@@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.0c\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-09-29 13:59+0000\n"
"PO-Revision-Date: 2016-09-29 13:59+0000\n"
"POT-Creation-Date: 2016-12-22 13:48+0000\n"
"PO-Revision-Date: 2016-12-22 13:48+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@@ -41,6 +41,12 @@ msgstr ""
msgid "&nbsp;"
msgstr ""
#. module: point_of_sale
#: code:addons/point_of_sale/models/pos_order.py:72
#, python-format
msgid "(RESCUE FOR %(session)s)"
msgstr ""
#. module: point_of_sale
#: model:ir.ui.view,arch_db:point_of_sale.view_pos_pos_form
msgid "(update)"
@@ -253,11 +259,6 @@ msgstr ""
msgid "Acsone.eu"
msgstr ""
#. module: point_of_sale
#: model:ir.model.fields,field_description:point_of_sale.field_res_users_activation
msgid "Activation"
msgstr ""
#. module: point_of_sale
#: model:ir.model.fields,field_description:point_of_sale.field_pos_config_active
msgid "Active"
@@ -513,8 +514,10 @@ msgid "Carrots"
msgstr ""
#. module: point_of_sale
#: code:addons/point_of_sale/models/pos_session.py:285
#: model:ir.model.fields,field_description:point_of_sale.field_pos_config_cash_control
#: model:ir.ui.view,arch_db:point_of_sale.view_pos_config_form
#, python-format
msgid "Cash Control"
msgstr ""
@@ -1218,11 +1221,6 @@ msgstr ""
msgid "General Information"
msgstr ""
#. module: point_of_sale
#: model:ir.model.fields,help:point_of_sale.field_res_users_partner_weight
msgid "Gives the probability to assign a lead to this partner. (0 means no assignation.)"
msgstr ""
#. module: point_of_sale
#: model:ir.model.fields,help:point_of_sale.field_pos_category_sequence
msgid "Gives the sequence order when displaying a list of product categories."
@@ -1329,16 +1327,6 @@ msgstr ""
msgid "Image"
msgstr ""
#. module: point_of_sale
#: model:ir.model.fields,field_description:point_of_sale.field_res_users_implemented_partner_ids
msgid "Implementation References"
msgstr ""
#. module: point_of_sale
#: model:ir.model.fields,field_description:point_of_sale.field_res_users_assigned_partner_id
msgid "Implemented by"
msgstr ""
#. module: point_of_sale
#. openerp-web
#: code:addons/point_of_sale/static/src/xml/pos.xml:1312
@@ -1531,16 +1519,6 @@ msgstr ""
msgid "Lemon"
msgstr ""
#. module: point_of_sale
#: model:ir.model.fields,field_description:point_of_sale.field_res_users_grade_id
msgid "Level"
msgstr ""
#. module: point_of_sale
#: model:ir.model.fields,field_description:point_of_sale.field_res_users_partner_weight
msgid "Level Weight"
msgstr ""
#. module: point_of_sale
#: model:ir.model.fields,field_description:point_of_sale.field_pos_order_line_name
msgid "Line No"
@@ -1712,12 +1690,7 @@ msgid "Next Order"
msgstr ""
#. module: point_of_sale
#: model:ir.model.fields,field_description:point_of_sale.field_res_users_date_review_next
msgid "Next Partner Review"
msgstr ""
#. module: point_of_sale
#: code:addons/point_of_sale/models/pos_order.py:136
#: code:addons/point_of_sale/models/pos_order.py:129
#, python-format
msgid "No cash statement found for this session. Unable to record returned cash."
msgstr ""
@@ -2041,11 +2014,6 @@ msgstr ""
msgid "Partner Services"
msgstr ""
#. module: point_of_sale
#: model:ir.model.fields,field_description:point_of_sale.field_res_users_date_partnership
msgid "Partnership Date"
msgstr ""
#. module: point_of_sale
#. openerp-web
#: code:addons/point_of_sale/static/src/js/gui.js:263
+17 -26
View File
@@ -53,36 +53,29 @@ class PosOrder(models.Model):
}
# This deals with orders that belong to a closed session. In order
# to recover from this we:
# - assign the order to another compatible open session
# - if that doesn't exist, create a new one
# to recover from this situation we create a new rescue session,
# making it obvious that something went wrong.
# A new, separate, rescue session is preferred for every such recovery,
# to avoid adding unrelated orders to live sessions.
def _get_valid_session(self, order):
PosSession = self.env['pos.session']
closed_session = PosSession.browse(order['pos_session_id'])
open_session = PosSession.search(
[('state', '=', 'opened'),
('config_id', '=', closed_session.config_id.id),
('user_id', '=', closed_session.user_id.id)],
limit=1, order="start_at DESC")
_logger.warning('session %s (ID: %s) was closed but received order %s (total: %s) belonging to it',
closed_session.name,
closed_session.id,
order['name'],
order['amount_total'])
_logger.warning('attempting to create recovery session for saving order %s', order['name'])
new_session = PosSession.create({
'config_id': closed_session.config_id.id,
'name': _('(RESCUE FOR %(session)s)') % {'session': closed_session.name},
'rescue': True, # avoid conflict with live sessions
})
# bypass opening_control (necessary when using cash control)
new_session.action_pos_session_open()
if open_session:
_logger.warning('using session %s (ID: %s) for order %s instead',
open_session.name,
open_session.id,
order['name'])
return open_session
else:
_logger.warning('attempting to create new session for order %s', order['name'])
new_session = PosSession.create({'config_id': closed_session.config_id.id})
# bypass opening_control (necessary when using cash control)
new_session.action_pos_session_open()
return new_session
return new_session
def _match_payment_to_invoice(self, order):
account_precision = self.env['decimal.precision'].precision_get('Account')
@@ -408,7 +401,7 @@ class PosOrder(models.Model):
# set name based on the sequence specified on the config
session = self.env['pos.session'].browse(values['session_id'])
values['name'] = session.config_id.sequence_id._next()
values.setdefault('session_id', session.config_id.pricelist_id.id)
values.setdefault('pricelist_id', session.config_id.pricelist_id.id)
else:
# fallback on any pos.order sequence
values['name'] = self.env['ir.sequence'].next_by_code('pos.order')
@@ -911,9 +904,7 @@ class ReportSaleDetails(models.AbstractModel):
@api.multi
def render_html(self, docids, data=None):
company = request.env.user.company_id
date_start = self.env.context.get('date_start', False)
date_stop = self.env.context.get('date_stop', False)
data = dict(data or {}, date_start=date_start, date_stop=date_stop)
data.update(self.get_sale_details(date_start, date_stop, company))
data = dict(data or {})
configs = self.env['pos.config'].browse(data['config_ids'])
data.update(self.get_sale_details(data['date_start'], data['date_stop'], configs))
return self.env['report'].render('point_of_sale.report_saledetails', data)
@@ -165,6 +165,8 @@ class PosSession(models.Model):
pos_config.sudo().write({'journal_ids': [(6, 0, journals.ids)]})
pos_name = self.env['ir.sequence'].with_context(ctx).next_by_code('pos.session')
if values.get('name'):
pos_name += ' ' + values['name']
statements = []
ABS = self.env['account.bank.statement']
@@ -273,3 +275,33 @@ class PosSession(models.Model):
'target': 'self',
'url': '/pos/web/',
}
@api.multi
def open_cashbox(self):
self.ensure_one()
context = dict(self._context)
balance_type = context.get('balance') or 'start'
context['bank_statement_id'] = self.cash_register_id.id
context['balance'] = balance_type
context['default_pos_id'] = self.config_id.id
action = {
'name': _('Cash Control'),
'view_type': 'form',
'view_mode': 'form',
'res_model': 'account.bank.statement.cashbox',
'view_id': self.env.ref('account.view_account_bnk_stmt_cashbox').id,
'type': 'ir.actions.act_window',
'context': context,
'target': 'new'
}
cashbox_id = None
if balance_type == 'start':
cashbox_id = self.cash_register_id.cashbox_start_id.id
else:
cashbox_id = self.cash_register_id.cashbox_end_id.id
if cashbox_id:
action['res_id'] = cashbox_id
return action
+1 -1
View File
@@ -114,7 +114,7 @@ var Gui = core.Class.extend({
this.current_screen.hide();
}
this.current_screen = screen;
this.current_screen.show();
this.current_screen.show(refresh);
}
},
+2 -1
View File
@@ -1075,7 +1075,8 @@ exports.Orderline = Backbone.Model.extend({
init_from_JSON: function(json) {
this.product = this.pos.db.get_product_by_id(json.product_id);
if (!this.product) {
console.error('ERROR: attempting to recover product not available in the point of sale');
console.error('ERROR: attempting to recover product ID', json.product_id,
'not available in the point of sale. Correct the product or clean the browser cache.');
}
this.set_product_lot(this.product)
this.price = json.price_unit;
@@ -272,8 +272,9 @@ var ScaleScreenWidget = ScreenWidget.extend({
});
this.$('.next,.buy-product').click(function(){
self.order_product();
self.gui.show_screen(self.next_screen);
// add product *after* switching screen to scroll properly
self.order_product();
});
queue.schedule(function(){
@@ -964,10 +965,12 @@ var ProductScreenWidget = ScreenWidget.extend({
}
},
show: function(){
show: function(reset){
this._super();
this.product_categories_widget.reset_category();
this.numpad.state.reset();
if (reset) {
this.product_categories_widget.reset_category();
this.numpad.state.reset();
}
},
close: function(){
@@ -48,18 +48,18 @@
<span class="o_stat_text">Money Out</span>
</div>
</button>
<button name="%(account.action_view_account_bnk_stmt_cashbox)d"
<button name="open_cashbox"
class="oe_stat_button"
attrs="{'invisible':['|', ('cash_control', '=', False), ('state', '!=', 'opening_control')]}"
icon="fa-money"
type="action" context="{'balance': 'start', 'bank_statement_id': cash_register_id, 'default_pos_id': config_id}">
type="object" context="{'balance': 'start'}">
<span class="o_stat_text">Set Opening Balance</span>
</button>
<button name="%(account.action_view_account_bnk_stmt_cashbox)d"
<button name="open_cashbox"
class="oe_stat_button"
attrs="{'invisible':['|', '|', ('cash_control', '=', False), ('state', '=', 'opening_control'), ('state', '=', 'closed')]}"
icon="fa-money"
type="action" context="{'balance': 'end', 'bank_statement_id': cash_register_id, 'default_pos_id': config_id}">
type="object" context="{'balance': 'end'}">
<span class="o_stat_text">Set Closing Balance</span>
</button>
<button class="oe_stat_button" name="action_stock_picking" type="object" icon="fa-arrows-v" attrs="{'invisible':[('picking_count', '=', 0)]}">
+20 -4
View File
@@ -9,7 +9,25 @@ class PosDetails(models.TransientModel):
_name = 'pos.details.wizard'
_description = 'Open Sales Details Report'
start_date = fields.Datetime(required=True, default=fields.Datetime.now)
def _default_start_date(self):
""" Find the earliest start_date of the latests sessions """
# restrict to configs available to the user
config_ids = self.env['pos.config'].search([]).ids
# exclude configs has not been opened for 2 days
self.env.cr.execute("""
SELECT
max(start_at) as start,
config_id
FROM pos_session
WHERE config_id = ANY(%s)
AND start_at > (NOW() - INTERVAL '2 DAYS')
GROUP BY config_id
""", (config_ids,))
latest_start_dates = [res['start'] for res in self.env.cr.dictfetchall()]
# earliest of the latest sessions
return latest_start_dates and min(latest_start_dates) or fields.Datetime.now()
start_date = fields.Datetime(required=True, default=_default_start_date)
end_date = fields.Datetime(required=True, default=fields.Datetime.now)
pos_config_ids = fields.Many2many('pos.config', 'pos_detail_configs',
default=lambda s: s.env['pos.config'].search([]))
@@ -26,8 +44,6 @@ class PosDetails(models.TransientModel):
@api.multi
def generate_report(self):
data = {'date_start': self.start_date, 'date_stop': self.end_date}
data.update(self.env['report.point_of_sale.report_saledetails'].get_sale_details(
self.start_date, self.end_date, self.pos_config_ids))
data = {'date_start': self.start_date, 'date_stop': self.end_date, 'config_ids': self.pos_config_ids.ids}
return self.env['report'].get_action(
[], 'point_of_sale.report_saledetails', data=data)
@@ -54,7 +54,14 @@ models.load_models({
for(var i = 0; i < printers.length; i++){
if(active_printers[printers[i].id]){
var printer = new Printer(self,{url:'http://'+printers[i].proxy_ip+':8069'});
var url = printers[i].proxy_ip;
if(url.indexOf('//') < 0){
url = 'http://'+url;
}
if(url.indexOf(':',url.indexOf('//')+2) < 0){
url = url+':8069';
}
var printer = new Printer(self,{url:url});
printer.config = printers[i];
self.printers.push(printer);
+1 -1
View File
@@ -105,7 +105,7 @@ class ProductProduct(models.Model):
_description = "Product"
_inherits = {'product.template': 'product_tmpl_id'}
_inherit = ['mail.thread']
_order = 'default_code'
_order = 'default_code, id'
price = fields.Float(
'Price', compute='_compute_product_price',
@@ -301,6 +301,8 @@ class Pricelist(models.Model):
p = Partner.browse(partner_id)
pl = Property.get('property_product_pricelist', Partner._name, '%s,%s' % (Partner._name, p.id))
if pl:
pl = pl[0].id
if not pl:
if p.country_id.code:
@@ -50,7 +50,7 @@ class ProductProduct(models.Model):
"""
Inherit read_group to calculate the sum of the non-stored fields, as it is not automatically done anymore through the XML.
"""
res = super(ProductProduct, self).read_group(fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
res = super(ProductProduct, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
fields_list = ['turnover', 'sale_avg_price', 'sale_purchase_price', 'sale_num_invoiced', 'purchase_num_invoiced',
'sales_gap', 'purchase_gap', 'total_cost', 'sale_expected', 'normal_cost', 'total_margin',
'expected_margin', 'total_margin_rate', 'expected_margin_rate']
@@ -66,7 +66,7 @@ class ProductProduct(models.Model):
for prod in products:
prod_re[prod] = re_ind
re_ind += 1
res_val = tot_products._compute_product_margin_fields_values([x for x in fields if fields in fields_list], '')
res_val = tot_products._compute_product_margin_fields_values(field_names=[x for x in fields if fields in fields_list])
for key in res_val.keys():
for l in res_val[key].keys():
re = res[prod_re[key]]
@@ -144,4 +144,6 @@ class ProductProduct(models.Model):
res[val.id]['total_margin_rate'] = res[val.id]['turnover'] and res[val.id]['total_margin'] * 100 / res[val.id]['turnover'] or 0.0
if 'expected_margin_rate' in field_names:
res[val.id]['expected_margin_rate'] = res[val.id]['sale_expected'] and res[val.id]['expected_margin'] * 100 / res[val.id]['sale_expected'] or 0.0
for k, v in res[val.id].items():
setattr(val, k, v)
return res
+1 -1
View File
@@ -105,7 +105,7 @@ class ProjectIssue(models.Model):
def _compute_inactivity_days(self):
current_datetime = fields.Datetime.from_string(fields.Datetime.now())
for issue in self:
dt_create_date = fields.Datetime.from_string(issue.create_date)
dt_create_date = fields.Datetime.from_string(issue.create_date) or current_datetime
issue.days_since_creation = (current_datetime - dt_create_date).days
if issue.date_action_last:
-1
View File
@@ -2,7 +2,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import account_invoice
import mail_mail
import purchase
import res_company
import res_config
-18
View File
@@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
class MailMail(models.Model):
_name = 'mail.mail'
_inherit = 'mail.mail'
@api.multi
def _postprocess_sent_message(self, mail_sent=True):
for mail in self:
if mail_sent and mail.model == 'purchase.order':
purchase_order = self.env['purchase.order'].browse(mail.res_id)
if purchase_order.state == 'draft':
purchase_order.state = 'sent'
return super(MailMail, self)._postprocess_sent_message(mail_sent=mail_sent)
+2 -5
View File
@@ -1065,12 +1065,9 @@ class ProductProduct(models.Model):
('state', 'in', ['purchase', 'done']),
('product_id', 'in', self.mapped('id')),
]
r = {}
for group in self.env['purchase.report'].read_group(domain, ['product_id', 'unit_quantity'], ['product_id']):
r[group['product_id'][0]] = group['unit_quantity']
PurchaseOrderLines = self.env['purchase.order.line'].search(domain)
for product in self:
product.purchase_count = r.get(product.id, 0)
return True
product.purchase_count = len(PurchaseOrderLines.filtered(lambda r: r.product_id == product).mapped('order_id'))
purchase_count = fields.Integer(compute='_purchase_count', string='# Purchases')
+1 -1
View File
@@ -124,7 +124,7 @@ class StockWarehouse(models.Model):
return routes
@api.multi
def _update_name_and_code(self, name, code):
def _update_name_and_code(self, name=False, code=False):
res = super(StockWarehouse, self)._update_name_and_code(name, code)
warehouse = self[0]
#change the buy procurement rule name
+1
View File
@@ -397,6 +397,7 @@
<field name="origin"/>
<field name="amount_untaxed" sum="Total Untaxed amount" string="Untaxed" widget="monetary"/>
<field name="amount_total" sum="Total amount" widget="monetary"/>
<field name="currency_id" invisible="1"/>
<field name="state"/>
<field name="invoice_status" invisible="not context.get('show_purchase', False)"/>
</tree>
+1 -1
View File
@@ -262,7 +262,7 @@ class Report(models.Model):
return self._run_wkhtmltopdf(
headerhtml, footerhtml, contenthtml, context.get('landscape'),
paperformat, specific_paperformat_args, save_in_attachment,
context.get('set_viewport_size')
context.get('set_viewport_size'),
)
@api.noguess
+9 -6
View File
@@ -151,14 +151,17 @@ class ResourceCalendar(models.Model):
# --------------------------------------------------
@api.multi
def get_attendances_for_weekday(self, date):
""" Given a list of weekdays, return matching resource.calendar.attendance"""
def get_attendances_for_weekday(self, day_dt):
""" Given a day datetime, return matching attendances """
self.ensure_one()
weekday = date.weekday()
date = fields.Date.today()
weekday = day_dt.weekday()
attendances = self.env['resource.calendar.attendance']
for attendance in self.attendance_ids.filtered(lambda att: int(att.dayofweek) == weekday and
not ((att.date_from and date < att.date_from) or (att.date_to and date > att.date_to))):
for attendance in self.attendance_ids.filtered(
lambda att:
int(att.dayofweek) == weekday and
not (att.date_from and fields.Date.from_string(att.date_from) > day_dt.date()) and
not (att.date_to and fields.Date.from_string(att.date_to) < day_dt.date())):
attendances |= attendance
return attendances
+32
View File
@@ -162,6 +162,38 @@ class TestResource(TestResourceCommon):
self.assertEqual(intervals[1][0], Datetime.from_string('2013-02-19 12:00:00'), 'resource_calendar: wrong working intervals')
self.assertEqual(intervals[1][1], Datetime.from_string('2013-02-19 15:45:30'), 'resource_calendar: wrong working intervals')
def test_21_calendar_working_intervals_limited_attendances(self):
""" Test attendances limited in time. """
self.env['resource.calendar.attendance'].browse(self.att3_id).write({
'date_from': self.date2 + relativedelta(days=7),
'date_to': False,
})
intervals = self.calendar.get_working_intervals_of_day(start_dt=self.date2)
self.assertEqual(intervals, [(Datetime.from_string('2013-02-15 10:11:12'), Datetime.from_string('2013-02-15 13:00:00'))])
self.env['resource.calendar.attendance'].browse(self.att3_id).write({
'date_from': False,
'date_to': self.date2 - relativedelta(days=7),
})
intervals = self.calendar.get_working_intervals_of_day(start_dt=self.date2)
self.assertEqual(intervals, [(Datetime.from_string('2013-02-15 10:11:12'), Datetime.from_string('2013-02-15 13:00:00'))])
self.env['resource.calendar.attendance'].browse(self.att3_id).write({
'date_from': self.date2 + relativedelta(days=7),
'date_to': self.date2 - relativedelta(days=7),
})
intervals = self.calendar.get_working_intervals_of_day(start_dt=self.date2)
self.assertEqual(intervals, [(Datetime.from_string('2013-02-15 10:11:12'), Datetime.from_string('2013-02-15 13:00:00'))])
self.env['resource.calendar.attendance'].browse(self.att3_id).write({
'date_from': self.date2,
'date_to': self.date2,
})
intervals = self.calendar.get_working_intervals_of_day(start_dt=self.date2)
self.assertEqual(len(intervals), 2)
self.assertEqual(intervals[0], (Datetime.from_string('2013-02-15 10:11:12'), Datetime.from_string('2013-02-15 13:00:00')))
self.assertEqual(intervals[1], (Datetime.from_string('2013-02-15 16:00:00'), Datetime.from_string('2013-02-15 23:00:00')))
def test_30_calendar_working_days(self):
""" Testing calendar hours computation on a working day """
+1 -1
View File
@@ -507,7 +507,7 @@ class SaleOrder(models.Model):
res.setdefault(group, 0.0)
res[group] += tax.compute_all(line.price_reduce, quantity=line.product_uom_qty)['taxes'][0]['amount']
res = sorted(res.items(), key=lambda l: l[0].sequence)
res = map(lambda l: (l[0].name, formatLang(self.env, l[1], currency_obj=currency)), res)
res = map(lambda l: (l[0].name, l[1]), res)
return res
+2 -1
View File
@@ -146,7 +146,8 @@
<tr>
<td><span t-esc="amount_by_group[0] or 'Taxes'"/></td>
<td class="text-right">
<span t-esc="amount_by_group[1]"/>
<span t-esc="amount_by_group[1]"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'/>
</td>
</tr>
</t>
+2
View File
@@ -116,6 +116,7 @@
<field name="partner_id"/>
<field name="user_id"/>
<field name="amount_total" sum="Total Tax Included" widget="monetary"/>
<field name="currency_id" invisible="1"/>
<field name="invoice_status"/>
<field name="state" invisible="1"/>
</tree>
@@ -134,6 +135,7 @@
<field name="partner_id"/>
<field name="user_id"/>
<field name="amount_total" sum="Total Tax Included" widget="monetary"/>
<field name="currency_id" invisible="1"/>
<field name="state"/>
<field name="currency_id" invisible="True"/>
</tree>
+2 -1
View File
@@ -37,7 +37,8 @@ class SaleOrder(models.Model):
dt = order_datetime + timedelta(days=line.customer_lead or 0.0)
dates_list.append(dt)
if dates_list:
order.commitment_date = fields.Datetime.to_string(min(dates_list))
commit_date = min(dates_list) if order.picking_policy == 'direct' else max(dates_list)
order.commitment_date = fields.Datetime.to_string(commit_date)
def _compute_picking_ids(self):
super(SaleOrder, self)._compute_picking_ids()
@@ -233,3 +233,37 @@ class TestSaleStock(TestSale):
self.so.action_done()
self.assertEqual(self.so.invoice_status, 'invoiced', 'Sale Stock: so invoice_status should be "invoiced" when set to done')
def test_04_create_picking_update_saleorderline(self):
"""
Test that updating multiple sale order lines after a succesful delivery creates a single picking containing
the new move lines.
"""
# sell two products
item1 = self.products['prod_order']
item2 = self.products['prod_del']
self.so = self.env['sale.order'].create({
'partner_id': self.partner.id,
'order_line': [
(0, 0, {'name': item1.name, 'product_id': item1.id, 'product_uom_qty': 1, 'product_uom': item1.uom_id.id, 'price_unit': item1.list_price}),
(0, 0, {'name': item2.name, 'product_id': item2.id, 'product_uom_qty': 1, 'product_uom': item2.uom_id.id, 'price_unit': item2.list_price}),
],
})
self.so.action_confirm()
# deliver them
self.assertEquals(len(self.so.picking_ids), 1)
self.so.picking_ids[0].action_done()
self.assertEquals(self.so.picking_ids[0].state, "done")
# update the two original sale order lines
self.so.write({
'order_line': [
(1, self.so.order_line[0].id, {'product_uom_qty': 2}),
(1, self.so.order_line[1].id, {'product_uom_qty': 2}),
]
})
# a single picking should be created for the new delivery
self.assertEquals(len(self.so.picking_ids), 2)
+6
View File
@@ -6426,6 +6426,12 @@ msgstr ""
msgid "You may only return Done pickings"
msgstr ""
#. module: stock
#: code:addons/stock/models/stock_inventory.py:21
#, python-format
msgid "You must define a warehouse for the company: %s."
msgstr ""
#. module: stock
#: code:addons/stock/models/stock_move.py:553
#: code:addons/stock/models/stock_pack_operation.py:234
+10 -1
View File
@@ -11,6 +11,15 @@ class Inventory(models.Model):
_name = "stock.inventory"
_description = "Inventory"
@api.model
def _default_location_id(self):
company_user = self.env.user.company_id
warehouse = self.env['stock.warehouse'].search([('company_id', '=', company_user.id)], limit=1)
if warehouse:
return warehouse.lot_stock_id.id
else:
raise UserError(_('You must define a warehouse for the company: %s.') % (company_user.name,))
name = fields.Char(
'Inventory Reference',
readonly=True, required=True,
@@ -43,7 +52,7 @@ class Inventory(models.Model):
'stock.location', 'Inventoried Location',
readonly=True, required=True,
states={'draft': [('readonly', False)]},
default=lambda self: getattr(self.env.ref('stock.warehouse0', raise_if_not_found=False) or self.env['stock.warehouse'], 'lot_stock_id').id)
default=_default_location_id)
product_id = fields.Many2one(
'product.product', 'Inventoried Product',
readonly=True,
+8
View File
@@ -411,6 +411,14 @@ class StockMove(models.Model):
type (moves should already have them identical). Otherwise, create a new
picking to assign them to. """
Picking = self.env['stock.picking']
# If this method is called in batch by a write on a one2many and
# at some point had to create a picking, some next iterations could
# try to find back the created picking. As we look for it by searching
# on some computed fields, we have to force a recompute, else the
# record won't be found.
self.recompute()
for move in self:
picking = Picking.search([
('group_id', '=', move.group_id.id),
+1 -1
View File
@@ -381,7 +381,7 @@ class Picking(models.Model):
elif self.partner_id:
location_dest_id = self.partner_id.property_stock_customer.id
else:
customerloc, location_dest_id = self.env['stock.warehouse']._get_partner_locations()
location_dest_id, supplierloc = self.env['stock.warehouse']._get_partner_locations()
self.location_id = location_id
self.location_dest_id = location_dest_id
+2 -2
View File
@@ -93,9 +93,9 @@
<!-- Procurements are located in Warehouse menu hierarchy, MRP users should come to Stock application to use it. -->
<menuitem id="menu_stock_sched" name="Schedulers" parent="stock.menu_stock_root" groups="group_stock_manager" sequence="50"/>
<menuitem action="action_procurement_compute" id="menu_procurement_compute" parent="menu_stock_sched" sequence="2"/>
<menuitem action="action_procurement_compute" id="menu_procurement_compute" parent="menu_stock_sched" sequence="2" groups="base.group_no_one"/>
<menuitem action="procurement.action_compute_schedulers" id="menu_stock_proc_schedulers" parent="menu_stock_sched" sequence="1" groups="stock.group_stock_manager"/>
<menuitem id="menu_stock_procurement" name="Automatic Procurements" parent="stock.menu_stock_config_settings" sequence="5"/>
<menuitem action="procurement.procurement_order_action_exceptions" id="menu_stock_procurement_action" parent="stock.menu_warehouse_report" sequence="150" groups="stock.group_stock_manager"/>
<menuitem id="menu_procurement_rules" name="Global Procurement Rules" parent="stock.menu_routes_config" action="procrules" groups="base.group_no_one" sequence="3"/>
</odoo>
</odoo>
+1 -1
View File
@@ -389,7 +389,7 @@
<field name="res_model">product.template</field>
<field name="view_mode">kanban,tree,form</field>
<field name="view_type">form</field>
<field name="context">{"search_default_products": 1, 'default_type': 'product'}</field>
<field name="context">{"search_default_consumable": 1, 'default_type': 'product'}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to define a new product.
@@ -6,10 +6,12 @@
# - Order if the virtual stock of today is bellow the min of the defined order point
#
from odoo import api, models
from odoo import api, models, tools
import logging
import threading
_logger = logging.getLogger(__name__)
class ProcurementOrderpointConfirm(models.TransientModel):
_name = 'procurement.orderpoint.compute'
@@ -20,6 +22,17 @@ class ProcurementOrderpointConfirm(models.TransientModel):
# As this function is in a new thread, I need to open a new cursor, because the old one may be closed
new_cr = self.pool.cursor()
self = self.with_env(self.env(cr=new_cr))
scheduler_cron = self.sudo().env.ref('procurement.ir_cron_scheduler_action')
# Avoid to run the scheduler multiple times in the same time
try:
with tools.mute_logger('odoo.sql_db'):
self._cr.execute("SELECT id FROM ir_cron WHERE id = %s FOR UPDATE NOWAIT", (scheduler_cron.id,))
except Exception:
_logger.info('Attempt to run procurement scheduler aborted, as already running')
self._cr.rollback()
self._cr.close()
return {}
self.env['procurement.order']._procure_orderpoint_confirm(
use_new_cursor=new_cr.dbname,
company_id=self.env.user.company_id.id)
+1 -1
View File
@@ -102,7 +102,7 @@ class ProductProduct(models.Model):
product_accounts = {product.id: product.product_tmpl_id.get_product_accounts() for product in self}
for location in locations:
for product in self.with_context(location=location.id, compute_child=False):
for product in self.with_context(location=location.id, compute_child=False).filtered(lambda r: r.valuation == 'real_time'):
diff = product.standard_price - new_price
if float_is_zero(diff, precision_rounding=product.currency_id.rounding):
raise UserError(_("No difference between standard price and new price!"))
@@ -955,7 +955,8 @@ var FieldRadio = common.AbstractField.extend(common.ReinitializeFieldMixin, {
},
get_value: function () {
var value = this.get('value');
return ((value instanceof Array)? value[0] : value) || false;
value = ((value instanceof Array)? value[0] : value);
return _.isUndefined(value) ? false : value;
},
render_value: function () {
var self = this;
@@ -1006,6 +1007,7 @@ var FieldReference = common.AbstractField.extend(common.ReinitializeFieldMixin,
this.m2o = new FieldMany2One(this.fm, { attrs: {
name: 'Referenced Document',
modifiers: JSON.stringify({readonly: this.get('effective_readonly')}),
context: this.build_context().eval(),
}});
this.m2o.on("change:value", this, this.data_changed);
this.m2o.appendTo(this.$el);
+17 -8
View File
@@ -107,7 +107,8 @@ return Widget.extend({
display_bar: function () {
// prepare data for bar chart
var data, values,
measure = this.fields[this.measure].string;
measure = this.fields[this.measure].string,
self = this;
// zero groupbys
if (this.groupbys.length === 0) {
@@ -166,9 +167,8 @@ return Widget.extend({
svg.transition().duration(0);
var chart = nv.models.multiBarChart();
var maxVal = _.max(values, function(v) {return v.y})
chart.options({
margin: {left: 12 * String(maxVal && maxVal.y || 10000000).length},
margin: {left: 120, bottom: 60},
delay: 250,
transition: 10,
showLegend: _.size(data) <= MAX_LEGEND_LENGTH,
@@ -177,10 +177,15 @@ return Widget.extend({
rightAlignYAxis: false,
stacked: this.stacked,
reduceXTicks: false,
// rotateLabels: 40,
rotateLabels: -20,
showControls: (this.groupbys.length > 1)
});
chart.yAxis.tickFormat(function(d) { return formats.format_value(d, { type : 'float' });});
chart.yAxis.tickFormat(function(d) {
return formats.format_value(d, {
type : 'float',
digits : self.fields[self.measure] && self.fields[self.measure].digits || [69, 2],
});
});
chart(svg);
this.to_remove = chart.update;
@@ -302,9 +307,8 @@ return Widget.extend({
svg.transition().duration(0);
var chart = nv.models.lineChart();
var maxVal = _.max(values, function(v) {return v.y})
chart.options({
margin: {left: 12 * String(maxVal && maxVal.y || 10000000).length, right: 50},
margin: {left: 120, bottom: 60},
useInteractiveGuideline: true,
showLegend: _.size(data) <= MAX_LEGEND_LENGTH,
showXAxis: true,
@@ -312,7 +316,12 @@ return Widget.extend({
});
chart.xAxis.tickValues(tickValues)
.tickFormat(tickFormat);
chart.yAxis.tickFormat(function(d) { return formats.format_value(d, { type : 'float' });});
chart.yAxis.tickFormat(function(d) {
return formats.format_value(d, {
type : 'float',
digits : self.fields[self.measure] && self.fields[self.measure].digits || [69, 2],
});
});
chart(svg);
this.to_remove = chart.update;
@@ -127,6 +127,11 @@
visibility: visible;
}
&.o_list_record_delete {
opacity: 0.5;
pointer-events: none;
}
&.o_readonly {
visibility: visible;
color: transparent;
+3 -2
View File
@@ -26,9 +26,10 @@ class Blog(models.Model):
if 'active' in vals:
# archiving/unarchiving a blog does it on its posts, too
post_ids = self.env['blog.post'].with_context(active_test=False).search([
('blog_id', 'in', self)
('blog_id', 'in', self.ids)
])
post_ids.write({'active': vals['active']})
for blog_post in post_ids:
blog_post.active = vals['active']
return res
@api.multi
+4 -5
View File
@@ -50,15 +50,14 @@ class WebsiteCustomer(http.Controller):
if country_id:
domain += [('country_id', '=', country_id)]
if not any(x['country_id'][0] == country_id for x in countries if x['country_id']):
country = Country.browse(country_id).read(['name'])
if country:
curr_country = Country.browse(country_id)
if country_id not in (x['country_id'][0] for x in countries if x['country_id']):
if curr_country.exists():
countries.append({
'country_id_count': 0,
'country_id': (country_id, country['name'])
'country_id': (curr_country.id, curr_country.name)
})
countries.sort(key=lambda d: d['country_id'] and d['country_id'][1])
curr_country = Country.browse(country_id)
countries.insert(0, {
'country_id_count': country_count,
@@ -194,6 +194,10 @@ if(!$('.o_website_links_chart').length) {
charts.last_month_pie = new PieChart('#last_month_countries_charts', last_month_clicks_by_country);
charts.last_week_pie = new PieChart('#last_week_countries_charts', last_week_clicks_by_country);
var row_width = $('#all_time_countries_charts').parent().width();
var charts_svg = $('#all_time_countries_charts,last_month_countries_charts,last_week_countries_charts').find('svg');
charts_svg.css('height', Math.max(clicks_by_country.length * (row_width > 750 ? 1 : 2), 20) + 'em');
_.invoke(charts, 'start');
nv.utils.windowResize(function () {
@@ -84,14 +84,12 @@
<t t-set="object" t-value="message"/>
<t t-call="website.publish_short"/>
<div t-field="message.body"/>
<div class="o_mg_link_content">
<div class="col-md-2 col-sm-3 text-center" t-foreach='message.attachment_ids' t-as='attachment'>
<a t-attf-href="/web/content/#{attachment.id}?download=true" target="_blank">
<div class='oe_attachment_embedded o_image' t-att-title="attachment.name" t-att-data-mimetype="attachment.mimetype" t-attf-data-src="/web/image/#{attachment.id}/100x80"/>
<div class='oe_attachment_name'><t t-raw='attachment.name' /></div>
</a>
</div>
</div>
</div>
</div>
</li>
@@ -2,10 +2,11 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
from odoo.tools.translate import html_translate
class ProductTemplate(models.Model):
_inherit = "product.template"
website_description = fields.Html('Description for the website', sanitize_attributes=False) # hack, if website_sale is not installed
quote_description = fields.Html('Description for the quote', sanitize_attributes=False)
quote_description = fields.Html('Description for the quote', sanitize_attributes=False, translate=html_translate)
+9 -10
View File
@@ -81,11 +81,10 @@ class SaleOrder(models.Model):
def onchange_template_id(self):
if not self.template_id:
return
if self.partner_id:
self = self.with_context(lang=self.partner_id.lang)
template = self.template_id.with_context(lang=self.partner_id.lang)
order_lines = [(5, 0, 0)]
for line in self.template_id.quote_line:
for line in template.quote_line:
if self.pricelist_id:
price = self.pricelist_id.with_context(uom=line.product_uom_id.id).get_product_price(line.product_id, 1, False)
else:
@@ -111,7 +110,7 @@ class SaleOrder(models.Model):
self.order_line._compute_tax_id()
option_lines = []
for option in self.template_id.options:
for option in template.options:
if self.pricelist_id:
price = self.pricelist_id.with_context(uom=option.uom_id.id).get_product_price(option.product_id, 1, False)
else:
@@ -129,14 +128,14 @@ class SaleOrder(models.Model):
option_lines.append((0, 0, data))
self.options = option_lines
if self.template_id.number_of_days > 0:
self.validity_date = fields.Date.to_string(datetime.now() + timedelta(self.template_id.number_of_days))
if template.number_of_days > 0:
self.validity_date = fields.Date.to_string(datetime.now() + timedelta(template.number_of_days))
self.website_description = self.template_id.website_description
self.require_payment = self.template_id.require_payment
self.website_description = template.website_description
self.require_payment = template.require_payment
if self.template_id.note:
self.note = self.template_id.note
if template.note:
self.note = template.note
@api.multi
def open_quotation(self):
@@ -12,10 +12,8 @@ odoo.define('website_sale.rating', function (require) {
website_mail_thread.WebsiteMailThread.include({
prepend_message: function(message_data){
var rating_enabled = this.$el.data('rating-enabled');
message_data['rating_enabled'] = rating_enabled;
var $elem = this._super.call(this, message_data);
if(message_data['rating_enabled']){
if(message_data['rating_default_value']){
var rating_star = new rating.RatingStarWidget(this, {
'rating_default_value': message_data['rating_default_value'],
'rating_disabled': message_data['rating_disabled'],
+4 -4
View File
@@ -1,8 +1,8 @@
{# note: if odoo.com/page/docs sub-menu's structure change, these links have to change as well #}
<li><a href="http://www.odoo.com/documentation/user/9.0/index.html">User</a></li>
<li><a href="http://www.odoo.com/documentation/9.0/index.html">Developer</a></li>
<li><a href="http://www.odoo.com/documentation/9.0/api_integration.html">API</a></li>
<li><a href="http://www.odoo.com/documentation/9.0/setup/install.html">Installation</a></li>
<li><a href="http://www.odoo.com/documentation/user/10.0/index.html">User</a></li>
<li><a href="http://www.odoo.com/documentation/10.0/index.html">Developer</a></li>
<li><a href="http://www.odoo.com/documentation/10.0/api_integration.html">API</a></li>
<li><a href="http://www.odoo.com/documentation/10.0/setup/install.html">Installation</a></li>
<li><a href="http://www.odoo.com/page/odoo-white-paper">White Papers</a></li>
<li><a href="http://www.odoo.com/page/legal">Legal</a></li>
+15
View File
@@ -0,0 +1,15 @@
México, 2016-07-19
X8BIT 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,
Juan Carlos del Valle juan@x8bit.com https://github.com/imekinox
List of contributors:
Juan Carlos del Valle juan@x8bit.com https://github.com/imekinox
+11
View File
@@ -0,0 +1,11 @@
Denmark, 2016-05-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,
Hans Henrik Gabelgaard hhg@gabelgaard.org https://github.com/hhgabelgaard
+9
View File
@@ -0,0 +1,9 @@
Spain, 25/10/2016
I hereby agree to the terms of the Odoo Individual Contributor License Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this declaration.
Signed,
Iker LuqueMichel luquemichel@gmail.com https://github.com/iluque
+9
View File
@@ -0,0 +1,9 @@
China, 2016-10-19
I hereby agree to the terms of the Odoo Individual Contributor License Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this declaration.
Signed,
Henry Zhou zhouhenry@live.com https://github.com/jlzhou
+9
View File
@@ -0,0 +1,9 @@
China, 2016-10-12
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,
Liu Jianyun lnkdel@gmail.com https://github.com/lnkdel
+9
View File
@@ -0,0 +1,9 @@
Honduras, 2016-12-08
I hereby agree to the terms of the Odoo Individual Contributor License Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this declaration.
Signed,
Salvatore Josué Trimarchi Pinto salvatore.trimarchi@gmail.com https://github.com/salvatoretrimarchi
+11
View File
@@ -0,0 +1,11 @@
Dominican Republic, October 26, 2016
I hereby agree to the terms of the Odoo Individual Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Richard Aquino richard@smartup.do https://github.com/smartupdo
+1 -1
View File
@@ -211,7 +211,7 @@ class IrActionsReportXml(models.Model):
for report in self:
if report.ir_values_id:
try:
self.report.ir_values_id.sudo().unlink()
report.ir_values_id.sudo().unlink()
except Exception:
raise UserError(_('Deletion of the action record failed.'))
return True
+12 -1
View File
@@ -138,8 +138,17 @@ class IrAttachment(models.Model):
if self._storage() != 'file':
return
# prevent all concurrent updates on ir_attachment while collecting!
# Continue in a new transaction. The LOCK statement below must be the
# first one in the current transaction, otherwise the database snapshot
# used by it may not contain the most recent changes made to the table
# ir_attachment! Indeed, if concurrent transactions create attachments,
# the LOCK statement will wait until those concurrent transactions end.
# But this transaction will not see the new attachements if it has done
# other requests before the LOCK (like the method _storage() above).
cr = self._cr
cr.commit()
# prevent all concurrent updates on ir_attachment while collecting!
cr.execute("LOCK ir_attachment IN SHARE MODE")
# retrieve the file names from the checklist
@@ -168,6 +177,8 @@ class IrAttachment(models.Model):
with tools.ignore(OSError):
os.unlink(filepath)
# commit to release the lock
cr.commit()
_logger.info("filestore gc %d checked, %d removed", len(checklist), removed)
@api.depends('store_fname', 'db_datas')
+12
View File
@@ -435,6 +435,12 @@ class IrModelFields(models.Model):
model = self.env[record.model]
field = model._fields[record.name]
if field.type == 'many2one' and model._field_inverses.get(field):
if self._context.get(MODULE_UNINSTALL_FLAG):
# automatically unlink the corresponding one2many field(s)
inverses = self.search([('relation', '=', field.model_name),
('relation_field', '=', field.name)])
inverses.unlink()
continue
msg = _("The field '%s' cannot be removed because the field '%s' depends on it.")
raise UserError(msg % (field, model._field_inverses[field][0]))
@@ -714,6 +720,12 @@ class IrModelConstraint(models.Model):
self.unlink()
@api.multi
def copy(self, default=None):
default = dict(default or {})
default['name'] = self.name + '_copy'
return super(IrModelConstraint, self).copy(default)
class IrModelRelation(models.Model):
"""
+12
View File
@@ -1578,6 +1578,7 @@ class Datetime(Field):
class Binary(Field):
type = 'binary'
_slots = {
'prefetch': False, # not prefetched by default
'attachment': False, # whether value is stored in attachment
}
@@ -1747,6 +1748,14 @@ class Selection(Field):
return item[1]
return False
def convert_to_column(self, value, record):
""" Convert ``value`` from the ``write`` format to the SQL format. """
if value is None or value is False:
return None
if isinstance(value, unicode):
return value.encode('utf8')
return str(value)
class Reference(Selection):
type = 'reference'
@@ -2398,6 +2407,9 @@ class Many2many(_RelationalMulti):
class Serialized(Field):
""" Serialized fields provide the storage for sparse fields. """
type = 'serialized'
_slots = {
'prefetch': False, # not prefetched by default
}
column_type = ('text', 'text')
def convert_to_column(self, value, record):
+8 -3
View File
@@ -665,10 +665,11 @@ class BaseModel(object):
methods = []
for attr, func in getmembers(cls, is_constraint):
for name in func._constrains:
if name not in cls._fields:
field = cls._fields.get(name)
if not field:
_logger.warning("method %s.%s: @constrains parameter %r is not a field name", cls._name, attr, name)
if not cls._fields[name].store:
_logger.warning("method %s.%s: @constrains parameter %r is not stored", cls._name, attr, name)
if not (field.store or field.inverse):
_logger.warning("method %s.%s: @constrains parameter %r is not writeable", cls._name, attr, name)
methods.append(func)
# optimization: memoize result on cls, it will not be recomputed
@@ -3514,6 +3515,8 @@ class BaseModel(object):
record._cache.update(record._convert_to_cache(new_vals, update=True))
for key in new_vals:
self._fields[key].determine_inverse(self)
# check Python constraints for inversed fields
self._validate_fields(set(new_vals) - set(old_vals))
return True
@@ -3772,6 +3775,8 @@ class BaseModel(object):
with self.env.protecting(protected_fields, record):
for key in new_vals:
self._fields[key].determine_inverse(record)
# check Python constraints for inversed fields
record._validate_fields(set(new_vals) - set(old_vals))
return record

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