[MERGE] foward port of 10.0 up to revision 84a650e33a
This commit is contained in:
+1
-1
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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/>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'})
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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>
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 " "
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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)]}">
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 """
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user