[MERGE] forward port branch saas-14 up to be9dfb331f

This commit is contained in:
Christophe Simonis
2017-02-10 18:41:06 +01:00
150 changed files with 1435 additions and 536 deletions
+1
View File
@@ -51,6 +51,7 @@
<field name="prefix">A</field>
<field eval="1" name="number_next"/>
<field eval="1" name="number_increment"/>
<field eval="False" name="company_id"/>
</record>
<!--
Invoice requests (deprecated)
@@ -51,6 +51,27 @@
-
!python {model: account.invoice, id: demo_invoice_3}:
self.action_invoice_open()
-
Create an invoice that will generate a followup
-
!record {model: account.invoice, id: demo_invoice_followup}:
company_id: base.main_company
partner_id: base.res_partner_2
user_id: base.user_demo
reference_type: none
payment_term_id: account.account_payment_term_immediate
type: 'out_invoice'
date_invoice: !eval (datetime.today() + timedelta(days=-15)).strftime('%Y-%m-%d')
invoice_line_ids:
- product_id: product.consu_delivery_02
price_unit: 642.0
quantity: 5
- product_id: product.consu_delivery_03
price_unit: 280.0
quantity: 5.0
-
!python {model: account.invoice, id: demo_invoice_followup}:
self.action_invoice_open()
-
Set the context for supplier bills because the 'type' field is readonly in invoice form views, so it value won't be considered by the yaml import
-
+9 -5
View File
@@ -484,7 +484,6 @@ class AccountInvoice(models.Model):
addr = self.partner_id.address_get(['delivery'])
fiscal_position = self.env['account.fiscal.position'].get_fiscal_position(self.partner_id.id, delivery_id=addr['delivery'])
bank_id = p.bank_ids and p.bank_ids.ids[0] or False
# If partner has no warning, check its company
if p.invoice_warn == 'no-message' and p.parent_id:
@@ -505,8 +504,12 @@ class AccountInvoice(models.Model):
self.payment_term_id = payment_term_id
self.fiscal_position_id = fiscal_position
if type in ('in_invoice', 'in_refund'):
if type in ('in_invoice', 'out_refund'):
bank_ids = p.commercial_partner_id.bank_ids
bank_id = bank_ids[0].id if bank_ids else False
self.partner_bank_id = bank_id
return {'domain': {'partner_bank_id': [('id', 'in', bank_ids.ids)]}}
return {}
@api.onchange('journal_id')
@@ -524,7 +527,7 @@ class AccountInvoice(models.Model):
self.date_due = self.date_due or self.date_invoice
else:
pterm = self.payment_term_id
pterm_list = pterm.with_context(currency_id=self.currency_id.id).compute(value=1, date_ref=date_invoice)[0]
pterm_list = pterm.with_context(currency_id=self.company_id.currency_id.id).compute(value=1, date_ref=date_invoice)[0]
self.date_due = max(line[0] for line in pterm_list)
@api.multi
@@ -761,8 +764,9 @@ class AccountInvoice(models.Model):
will be grouped together if the journal has the 'group line' option. Of course a module
can add fields to invoice lines that would need to be tested too before merging lines
or not."""
return "%s-%s-%s-%s-%s-%s" % (
return "%s-%s-%s-%s-%s-%s-%s" % (
invoice_line['account_id'],
invoice_line.get('tax_ids', 'False'),
invoice_line.get('tax_line_id', 'False'),
invoice_line.get('product_id', 'False'),
invoice_line.get('analytic_account_id', 'False'),
@@ -822,7 +826,7 @@ class AccountInvoice(models.Model):
name = inv.name or '/'
if inv.payment_term_id:
totlines = inv.with_context(ctx).payment_term_id.with_context(currency_id=inv.currency_id.id).compute(total, date_invoice)[0]
totlines = inv.with_context(ctx).payment_term_id.with_context(currency_id=company_currency.id).compute(total, date_invoice)[0]
res_amount_currency = total_currency
ctx['date'] = date_invoice
for i, t in enumerate(totlines):
+20 -5
View File
@@ -709,22 +709,37 @@ class AccountMoveLine(models.Model):
line_currency = (line.currency_id and line.amount_currency) and line.currency_id or company_currency
amount_currency_str = ""
total_amount_currency_str = ""
if line_currency != company_currency and target_currency != company_currency:
if line_currency != company_currency and target_currency == line_currency:
# The payment currency is the invoice currency, but they are different than the company currency
# We use the `amount_currency` computed during the invoice validation, at the invoice date
# to avoid exchange gain/loss
# e.g. an invoice of 100€ must be paid with 100€, whatever the company currency and the exchange rates
total_amount = line.amount_currency
actual_debit = debit > 0 and amount_currency or 0.0
actual_credit = credit > 0 and -amount_currency or 0.0
currency = line_currency
else:
# Either:
# - the invoice, payment, company currencies are all the same,
# - the payment currency is the company currency, but the invoice currency is different,
# - the invoice currency is the company currency, but the payment currency is different,
# - the invoice, payment and company currencies are all different.
# For the two first cases, we can simply use the debit/credit of the invoice move line, which are always in the company currency,
# and this is what the target need.
# For the two last cases, we can use the debit/credit which are in the company currency, and then change them to the target currency
total_amount = abs(debit - credit)
actual_debit = debit > 0 and amount or 0.0
actual_credit = credit > 0 and -amount or 0.0
if line_currency != target_currency and target_currency != company_currency:
currency = company_currency
if line_currency != target_currency:
amount_currency_str = formatLang(self.env, abs(actual_debit or actual_credit), currency_obj=line_currency)
total_amount_currency_str = formatLang(self.env, total_amount, currency_obj=line_currency)
if currency != target_currency:
ctx = context.copy()
ctx.update({'date': target_date or line.date})
total_amount = line_currency.with_context(ctx).compute(total_amount, target_currency)
actual_debit = line_currency.with_context(ctx).compute(actual_debit, target_currency)
actual_credit = line_currency.with_context(ctx).compute(actual_credit, target_currency)
total_amount = currency.with_context(ctx).compute(total_amount, target_currency)
actual_debit = currency.with_context(ctx).compute(actual_debit, target_currency)
actual_credit = currency.with_context(ctx).compute(actual_credit, target_currency)
amount_str = formatLang(self.env, abs(actual_debit or actual_credit), currency_obj=target_currency)
total_amount_str = formatLang(self.env, total_amount, currency_obj=target_currency)
+2 -2
View File
@@ -21,7 +21,7 @@ class account_payment_method(models.Model):
_name = "account.payment.method"
_description = "Payment Methods"
name = fields.Char(required=True)
name = fields.Char(required=True, translate=True)
code = fields.Char(required=True) # For internal identification
payment_type = fields.Selection([('inbound', 'Inbound'), ('outbound', 'Outbound')], required=True)
@@ -400,7 +400,7 @@ class account_payment(models.Model):
# the writeoff debit and credit must be computed from the invoice residual in company currency
# minus the payment amount in company currency, and not from the payment difference in the payment currency
# to avoid loss of precision during the currency rate computations. See revision 20935462a0cabeb45480ce70114ff2f4e91eaf79 for a detailed example.
total_residual_company_signed = self._compute_total_invoices_amount()
total_residual_company_signed = sum(invoice.residual_company_signed for invoice in self.invoice_ids)
total_payment_company_signed = self.currency_id.with_context(date=self.payment_date).compute(self.amount, self.company_id.currency_id)
if self.invoice_ids[0].type in ['in_invoice', 'out_refund']:
amount_wo = total_payment_company_signed - total_residual_company_signed
@@ -79,6 +79,13 @@
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
<record id="invoice_analysis_comp_rule" model="ir.rule">
<field name="name">Invoice Analysis multi-company</field>
<field name="model_id" ref="model_account_invoice_report"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
<record id="account_fiscal_position_comp_rule" model="ir.rule">
<field name="name">Account fiscal Mapping company rule</field>
<field name="model_id" ref="model_account_fiscal_position"/>
@@ -61,7 +61,7 @@
<group>
<group>
<field name="product_id"
context="{'partner_id': parent.partner_id}"/>
context="parent and {'partner_id': parent.partner_id}"/>
<label for="quantity"/>
<div>
<field name="quantity" class="oe_inline"/>
+1
View File
@@ -9,6 +9,7 @@
report_type="qweb-pdf"
name="account.report_invoice"
file="account.report_invoice"
attachment_use="True"
attachment="(object.state in ('open','paid')) and ('INV'+(object.number or '').replace('/','')+'.pdf')"
/>
+1 -2
View File
@@ -1106,7 +1106,6 @@
<field name="move_id" required="False"/>
<newline/>
<field name="date_maturity"/>
<field name="date"/>
<field name="blocked"/>
<newline/>
<field name="tax_ids" readonly="1"/>
@@ -1195,7 +1194,7 @@
<filter string="Unposted" domain="[('move_id.state','=','draft')]" help="Unposted Journal Items"/>
<filter name="posted" string="Posted" domain="[('move_id.state','=','posted')]" help="Posted Journal Items"/>
<separator/>
<filter string="Unreconciled" domain="[('full_reconcile_id','=',False), ('account_id.reconcile','=',True)]" help="Journal items where matching number isn't set" name="unreconciled"/>
<filter string="Unreconciled" domain="[('full_reconcile_id', '=', False), ('balance','!=', 0), ('account_id.reconcile','=',True)]" help="Journal items where matching number isn't set" name="unreconciled"/>
<separator/>
<filter string="Payable" domain="[('account_id.internal_type', '=', 'payable')]" help="From Payable accounts" name="payable"/>
<filter string="Receivable" domain="[('account_id.internal_type', '=', 'receivable')]" help="From Receivable accounts" name="receivable"/>
+1 -1
View File
@@ -86,7 +86,7 @@
<div class="o_setting_left_pane">
<field name="module_l10n_eu_service"/>
</div>
<div class="o_setting_right_pane" >
<div class="o_setting_right_pane" name="l10n_eu_service_right_pane">
<label for="module_l10n_eu_service"/>
<div class="text-muted">
Apply right VAT rates for digital products sold in EU
+15 -6
View File
@@ -36,7 +36,7 @@ class AccountMoveLineReconcile(models.TransientModel):
credit = debit = 0
lines = self.env['account.move.line'].browse(context.get('active_ids', []))
for line in lines:
if not line.reconciled:
if not line.full_reconcile_id:
credit += line.credit
debit += line.debit
precision = self.env.user.company_id.currency_id.decimal_places
@@ -64,9 +64,13 @@ class AccountMoveLineReconcile(models.TransientModel):
if aml.currency_id.id == currency:
continue
raise UserError(_('Operation not allowed. You can only reconcile entries that share the same secondary currency or that don\'t have one. Edit your journal items or make another selection before proceeding any further.'))
#Don't consider entrires that are already reconciled
move_lines_filtered = move_lines.filtered(lambda aml: not aml.reconciled)
#Because we are making a full reconcilition in batch, we need to consider use cases as defined in the test test_manual_reconcile_wizard_opw678153
#So we force the reconciliation in company currency only at first, then in second pass the amounts in secondary currency.
move_lines.with_context(skip_full_reconcile_check='amount_currency_excluded', manual_full_reconcile_currency=currency).reconcile()
#So we force the reconciliation in company currency only at first
move_lines_filtered.with_context(skip_full_reconcile_check='amount_currency_excluded', manual_full_reconcile_currency=currency).reconcile()
#then in second pass the amounts in secondary currency, only if some lines are still not fully reconciled
move_lines_filtered = move_lines.filtered(lambda aml: not aml.reconciled)
if move_lines_filtered:
move_lines_filtered.with_context(skip_full_reconcile_check='amount_currency_only', manual_full_reconcile_currency=currency).reconcile()
@@ -123,13 +127,18 @@ class AccountMoveLineReconcileWriteoff(models.TransientModel):
if aml.currency_id.id == currency:
continue
raise UserError(_('Operation not allowed. You can only reconcile entries that share the same secondary currency or that don\'t have one. Edit your journal items or make another selection before proceeding any further.'))
#Don't consider entrires that are already reconciled
move_lines_filtered = move_lines.filtered(lambda aml: not aml.reconciled)
#Because we are making a full reconcilition in batch, we need to consider use cases as defined in the test test_manual_reconcile_wizard_opw678153
#So we force the reconciliation in company currency only at first, then in second pass the amounts in secondary currency.
#So we force the reconciliation in company currency only at first,
context['skip_full_reconcile_check'] = 'amount_currency_excluded'
context['manual_full_reconcile_currency'] = currency
writeoff = move_lines.with_context(context).reconcile(self.writeoff_acc_id, self.journal_id)
writeoff = move_lines_filtered.with_context(context).reconcile(self.writeoff_acc_id, self.journal_id)
#then in second pass the amounts in secondary currency, only if some lines are still not fully reconciled
move_lines_filtered = move_lines.filtered(lambda aml: not aml.reconciled)
if move_lines_filtered:
move_lines_filtered.with_context(skip_full_reconcile_check='amount_currency_only', manual_full_reconcile_currency=currency).reconcile()
(move_lines + writeoff).compute_full_after_batch_reconcile()
if not isinstance(writeoff, bool):
move_lines += writeoff
move_lines.compute_full_after_batch_reconcile()
return {'type': 'ir.actions.act_window_close'}
@@ -21,6 +21,19 @@
<label string="Last Day" class="col-md-4 o_light_label"/>
<field name="fiscalyear_last_month" class="col-md-4 oe_inline"/>
<field name="fiscalyear_last_day" class="col-md-4 oe_inline text-center"/>
</div>
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box" id="fiscalyear">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
<label string="Entry Locking"/>
<div class="text-muted">
Lock dates for period closing
</div>
<div class="content-group">
<div class="row mt16">
<label for="fiscalyear_lock_date" class="col-md-6 o_light_label"/>
<field name="fiscalyear_lock_date" class="col-md-6 oe_inline"/>
<label for="period_lock_date" class="col-md-6 o_light_label"/>
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate='1'>
<function id="default_generate_sales_team_alias"
model="ir.values" name="set_default"
eval="('sales.config.settings', 'generate_sales_team_alias', True)"/>
eval="('sale.config.settings', 'generate_sales_team_alias', True)"/>
</data>
</odoo>
+1 -1
View File
@@ -84,7 +84,7 @@ class Team(models.Model):
@api.model
def create(self, vals):
generate_alias_name = self.env['ir.values'].get_default('sales.config.settings', 'generate_sales_team_alias')
generate_alias_name = self.env['ir.values'].get_default('sale.config.settings', 'generate_sales_team_alias')
if generate_alias_name and not vals.get('alias_name'):
vals['alias_name'] = vals.get('name')
return super(Team, self).create(vals)
+2 -2
View File
@@ -30,7 +30,7 @@ class CRMSettings(models.TransientModel):
@api.model
def get_default_generate_sales_team_alias(self, fields):
return {
'generate_sales_team_alias': self.env['ir.values'].get_default('sales.config.settings', 'generate_sales_team_alias')
'generate_sales_team_alias': self.env['ir.values'].get_default('sale.config.settings', 'generate_sales_team_alias')
}
@api.multi
@@ -38,7 +38,7 @@ class CRMSettings(models.TransientModel):
IrValues = self.env['ir.values']
if self.env['res.users'].has_group('base.group_erp_manager'):
IrValues = IrValues.sudo()
IrValues.set_default('sales.config.settings', 'generate_sales_team_alias', self.generate_sales_team_alias)
IrValues.set_default('sale.config.settings', 'generate_sales_team_alias', self.generate_sales_team_alias)
@api.model
def get_default_alias_prefix(self, fields):
+3
View File
@@ -583,6 +583,9 @@
<filter string="Overdue Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all opportunities for which the next action date is before today"/>
<filter string="Overdue Opportunities" name="overdue_opp"
domain="[('date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all opportunities with a deadline for which the next action date is before today"/>
<separator/>
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
<filter string="Lost" domain="[('active', '=', False)]"/>
+1 -1
View File
@@ -39,7 +39,7 @@
<div attrs="{'invisible': [('alias_domain', 'not in', ['localhost', '', False])]}">
<button type="action"
name="base_setup.action_general_configuration"
string="Setup your domain alias" icon="fa-arrow-right" class="oe_link"/>
string="Use an External Email Server" icon="fa-arrow-right" class="oe_link"/>
</div>
</div>
</div>
+4
View File
@@ -193,6 +193,10 @@ class Employee(models.Model):
@api.multi
def write(self, vals):
if 'address_home_id' in vals:
account_id = vals.get('bank_account_id') or self.bank_account_id.id
if account_id:
self.env['res.partner.bank'].browse(account_id).partner_id = vals['address_home_id']
tools.image_resize_images(vals)
return super(Employee, self).write(vals)
+3 -2
View File
@@ -16,8 +16,9 @@ class Partner(models.Model):
try:
employee_group = self.env.ref('base.group_user')
for user in employee_group.users:
suggestions.append((user.partner_id.id, user.name, user.email))
hr_suggestions = [{'id': user.partner_id.id, 'name': user.name, 'email': user.email}
for user in employee_group.users]
suggestions.append(hr_suggestions)
return suggestions
except AccessError:
return suggestions
+1 -1
View File
@@ -80,7 +80,7 @@
<field name="country_id" options='{"no_open": True, "no_create": True}'/>
<field name="identification_id" groups="hr.group_hr_user"/>
<field name="passport_id" groups="hr.group_hr_user"/>
<field name="bank_account_id"/>
<field name="bank_account_id" context="{'default_partner_id': address_home_id}"/>
</group>
<group string="Contact Information">
<field name="address_home_id"
+2 -3
View File
@@ -233,7 +233,7 @@ class HrExpense(models.Model):
#convert eml into an osv-valid format
lines = map(lambda x: (0, 0, expense._prepare_move_line(x)), move_lines)
move.write({'line_ids': lines})
move.with_context(dont_create_taxes=True).write({'line_ids': lines})
expense.sheet_id.write({'account_move_id': move.id})
move.post()
if expense.payment_mode == 'company_account':
@@ -532,7 +532,6 @@ class HrExpenseSheet(models.Model):
@api.multi
def action_open_journal_entries(self):
res = self.env['ir.actions.act_window'].for_xml_id('account', 'action_move_journal_line')
refs = [x.name for x in self]
res['domain'] = [('ref', 'in', refs)]
res['domain'] = [('id', 'in', self.mapped('account_move_id').ids)]
res['context'] = {}
return res
+1 -1
View File
@@ -500,7 +500,7 @@
</div>
<div class="row">
<div class="col-xs-6 text-muted">
<span><t t-esc="record.employee_id.value"/> <t t-esc="record.date.value"/></span>
<span><t t-esc="record.employee_id.value"/> <t t-esc="record.accounting_date.value"/></span>
</div>
<div class="col-xs-6">
<span class="pull-right text-right">
+4 -1
View File
@@ -120,7 +120,10 @@ class HolidaysType(models.Model):
for record in self:
name = record.name
if not record.limit:
name = name + (' (%g remaining out of %g)' % (record.virtual_remaining_leaves or 0.0, record.max_leaves or 0.0))
name = "%(name)s (%(count)s)" % {
'name': name,
'count': _('%g remaining out of %g') % (record.virtual_remaining_leaves or 0.0, record.max_leaves or 0.0)
}
res.append((record.id, name))
return res
+1 -1
View File
@@ -82,7 +82,7 @@ class Usb(Escpos):
def _raw(self, msg):
""" Print any command sent in raw format """
if len(msg) != self.device.write(self.out_ep, msg, self.interface):
if len(msg) != self.device.write(self.out_ep, msg, self.interface, timeout=5000):
self.device.write(self.out_ep, self.errorText, self.interface)
raise TicketNotPrinted()
+12 -11
View File
@@ -53,6 +53,7 @@ var LivechatButton = Widget.extend({
this.channel = null;
this.chat_window = null;
this.messages = [];
this.server_url = server_url;
},
willStart: function () {
@@ -115,16 +116,15 @@ var LivechatButton = Widget.extend({
}
},
load_qweb_template: function(){
return $.when(
$.get('/mail/static/src/xml/chat_window.xml'),
$.get('/mail/static/src/xml/thread.xml'),
$.get('/im_livechat/static/src/xml/im_livechat.xml')
).then(function (chat_window, mail_thread, livechat) {
// results are triplets of [dom: XMLDocument, status: String, xhr: jqXHR]
QWeb.add_template(chat_window[0]);
QWeb.add_template(mail_thread[0]);
QWeb.add_template(livechat[0]);
var xml_files = ['/mail/static/src/xml/chat_window.xml',
'/mail/static/src/xml/thread.xml',
'/im_livechat/static/src/xml/im_livechat.xml'];
var defs = _.map(xml_files, function (tmpl) {
return session.rpc('/web/proxy/load', {path: tmpl}).then(function (xml) {
QWeb.add_template(xml);
});
});
return $.when.apply($, defs);
},
open_chat: _.debounce(function () {
@@ -237,10 +237,11 @@ var LivechatButton = Widget.extend({
this.options.default_username;
// Compute the avatar_url
msg.avatar_src = this.server_url;
if (msg.author_id && msg.author_id[0]) {
msg.avatar_src = "/web/image/res.partner/" + msg.author_id[0] + "/image_small";
msg.avatar_src += "/web/image/res.partner/" + msg.author_id[0] + "/image_small";
} else {
msg.avatar_src = "/mail/static/src/img/smiley/avatar.jpg";
msg.avatar_src += "/mail/static/src/img/smiley/avatar.jpg";
}
if (options && options.prepend) {
@@ -16,6 +16,7 @@
border-bottom: 0px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
z-index: 5;
}
.o_chat_window {
@@ -23,7 +23,7 @@ class AccountInvoice(models.Model):
def _get_reference_type(self):
"""Add BBA Structured Communication Type and change labels from 'reference' into 'communication' """
res = super(AccountInvoice, self)._get_reference_type()
res[[i for i, x in enumerate(res) if x[0] == 'none'][0]] = ('none', 'Free Communication')
res[[i for i, x in enumerate(res) if x[0] == 'none'][0]] = ('none', _('Free Communication'))
res.append(('bba', 'BBA Structured Communication'))
return res
+2 -1
View File
@@ -56,6 +56,7 @@ References
'data': [
'security/ir.model.access.csv',
'wizard/wizard.xml',
'data/l10n_eu_service.service_tax_rate.csv'
'data/l10n_eu_service.service_tax_rate.csv',
'views/account_settings_config_views.xml'
],
}
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="account_config_settings_view_form" model="ir.ui.view">
<field name="name">account.config.settings.form</field>
<field name="model">account.config.settings</field>
<field name="inherit_id" ref="account.account_config_settings_view_form"/>
<field name="arch" type="xml">
<div name="l10n_eu_service_right_pane" position="inside">
<div class="content-group">
<div>
<button name="%(l10n_eu_service.action_eu_service)d" icon="fa-arrow-right" type="action" string="Add new countries" class="btn-link"/>
</div>
</div>
</div>
</field>
</record>
</odoo>
+1 -1
View File
@@ -12,7 +12,7 @@
<field name="name">Plantilla de cuentas de Guatemala (sencilla)</field>
<field name="bank_account_code_prefix">1.0.01.0</field>
<field name="cash_account_code_prefix">1.0.02.0</field>
<field name="code_digits">6</field>
<field name="code_digits">9</field>
<field name="currency_id" ref="base.QTQ"/>
<field name="transfer_account_id" ref="transfer_account_id"/>
</record>
@@ -484,7 +484,7 @@
<field name="account_id" ref="p11232"/>
<field name="refund_account_id" ref="p20011"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
<field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_33'), ref('st_tag_in_74')])]"/>
<field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_26'), ref('st_tag_in_58')])]"/>
</record>
<!--output purchase tax-->
@@ -498,7 +498,7 @@
<field name="account_id" ref="p10050"/>
<field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
<field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_22'), ref('st_tag_in_67')])]"/>
<field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_51'), ref('st_tag_in_15')])]"/>
</record>
<record id="output_tax_12" model="account.tax.template">
<field name="name">(standard)Output Purchase Tax @ 12%</field>
@@ -510,7 +510,7 @@
<field name="account_id" ref="p10050"/>
<field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
<field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_19'), ref('st_tag_in_64')])]"/>
<field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_12'), ref('st_tag_in_48')])]"/>
</record>
<record id="output_tax_4" model="account.tax.template">
<field name="name">(standard)Output Purchase Tax @ 4%</field>
@@ -1,5 +1,5 @@
"id","amount_select","condition_range_min","condition_range_max","amount_percentage","amount_fix","name","category_id/id","sequence","code","parent_rule_id/id","condition_select","condition_range","amount_percentage_base"
1,"fix",1,1,,100,"Education Allowance For One Child",hr_payroll.ALW,23,"CHEAONE","hr_payroll_rule_child1","range","employee.children",
2,"fix",2,10,,200,"Education Allowance For Two Child",hr_payroll.ALW,24,"CHEATWO","hr_payroll_rule_child1","range","employee.children",
2,"fix",2,10,,200,"Education Allowance For Two Children",hr_payroll.ALW,24,"CHEATWO","hr_payroll_rule_child1","range","employee.children",
3,"fix",1,1,,300,"Child Hostel Allowance For One Child",hr_payroll.ALW,26,"CHOONE","hr_payroll_rule_child2","range","employee.children",
4,"fix",2,10,,600,"Child Hostel Allowance For Two Child",hr_payroll.ALW,27,"CHOTWO","hr_payroll_rule_child2","range","employee.children",
4,"fix",2,10,,600,"Child Hostel Allowance For Two Children",hr_payroll.ALW,27,"CHOTWO","hr_payroll_rule_child2","range","employee.children",
1 id amount_select condition_range_min condition_range_max amount_percentage amount_fix name category_id/id sequence code parent_rule_id/id condition_select condition_range amount_percentage_base
2 1 fix 1 1 100 Education Allowance For One Child hr_payroll.ALW 23 CHEAONE hr_payroll_rule_child1 range employee.children
3 2 fix 2 10 200 Education Allowance For Two Child Education Allowance For Two Children hr_payroll.ALW 24 CHEATWO hr_payroll_rule_child1 range employee.children
4 3 fix 1 1 300 Child Hostel Allowance For One Child hr_payroll.ALW 26 CHOONE hr_payroll_rule_child2 range employee.children
5 4 fix 2 10 600 Child Hostel Allowance For Two Child Child Hostel Allowance For Two Children hr_payroll.ALW 27 CHOTWO hr_payroll_rule_child2 range employee.children
@@ -70,7 +70,7 @@
<field name="code">HRAMN</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result = (payslip.company_id.dearness_allowance and
((contract.wage + DA) * contract.house_rent_allowance_metro_nonmetro / 100)
((contract.wage + categories.DA) * contract.house_rent_allowance_metro_nonmetro / 100)
or (contract.wage * contract.house_rent_allowance_metro_nonmetro / 100))</field>
<field name="category_id" ref="hr_payroll.ALW"/>
<field name="name">House Rent Allowance</field>
@@ -130,7 +130,7 @@ or (contract.wage * contract.house_rent_allowance_metro_nonmetro / 100))</field>
<field name="condition_select">none</field>
<field name="sequence" eval="21"/>
<field name="amount_select">code</field>
<field name="amount_python_compute">result = (payslip.company_id.dearness_allowance and ((contract.wage + DA) * 0.10)
<field name="amount_python_compute">result = (payslip.company_id.dearness_allowance and ((contract.wage + categories.DA) * 0.10)
or (contract.wage * 0.10))</field>
<field name="note">This allowance is paid to Employees who are posted in big cities. The purpose is to compensate the high cost of living in cities like Mumbai, Delhi, etc. However it is Fully Taxable.</field>
</record>
@@ -365,7 +365,7 @@ else -0)</field>
<field name="name">Employee's PF Contribution</field>
<field name="category_id" ref="hr_payroll.DED"/>
<field name="amount_select">code</field>
<field name="amount_python_compute">result = (payslip.company_id.dearness_allowance and - ((contract.wage + DA) * 0.12)
<field name="amount_python_compute">result = (payslip.company_id.dearness_allowance and - ((contract.wage + categories.DA) * 0.12)
or - (contract.wage * 0.12))</field>
<field name= "note">Employer contribution does not become part of employee’s income and hence income tax is not payable on this part.</field>
<field name="register_id" ref="hr_register_provident_fund"/>
@@ -377,7 +377,7 @@ or - (contract.wage * 0.12))</field>
<field name="name">Employee's NPS Contribution</field>
<field name="category_id" ref="hr_payroll.DED"/>
<field name="amount_select">code</field>
<field name="amount_python_compute">result = (payslip.company_id.dearness_allowance and - ((contract.wage + DA) * 0.10)
<field name="amount_python_compute">result = (payslip.company_id.dearness_allowance and - ((contract.wage + categories.DA) * 0.10)
or - (contract.wage * 0.10))</field>
<field name="sequence" eval="155"/>
<field name="register_id" ref="hr_nps_contribution_register"/>
@@ -392,7 +392,7 @@ or - (contract.wage * 0.10))</field>
<field name="condition_python">result = bool(contract.voluntary_provident_fund)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result = (payslip.company_id.dearness_allowance and
- ((contract.wage + DA) * contract.voluntary_provident_fund / 100)
- ((contract.wage + categories.DA) * contract.voluntary_provident_fund / 100)
or - (contract.wage * contract.voluntary_provident_fund / 100))</field>
<field name="sequence" eval="160"/>
<field name="register_id" ref="hr_vpf_contribution_register"/>
@@ -498,7 +498,7 @@ or - (contract.wage * contract.voluntary_provident_fund / 100))</field>
<field name="name">Employer's NPS Contribution</field>
<field name="category_id" ref="hr_payroll.DED"/>
<field name="amount_select">code</field>
<field name="amount_python_compute">result = (payslip.company_id.dearness_allowance and - ((contract.wage + DA) * 0.10)
<field name="amount_python_compute">result = (payslip.company_id.dearness_allowance and - ((contract.wage + categories.DA) * 0.10)
or - (contract.wage * 0.10))</field>
<field name="sequence" eval="190"/>
<field name="register_id" ref="hr_nps_contribution_register"/>
@@ -511,7 +511,7 @@ or - (contract.wage * 0.10))</field>
<field name="name">Employer's PF Contribution</field>
<field name="category_id" ref="hr_payroll.DED"/>
<field name="amount_select">code</field>
<field name="amount_python_compute">result = (payslip.company_id.dearness_allowance and - ((contract.wage + DA) * 0.12)
<field name="amount_python_compute">result = (payslip.company_id.dearness_allowance and - ((contract.wage + categories.DA) * 0.12)
or - (contract.wage * 0.12))</field>
<field name="sequence" eval="195"/>
<field name="register_id" ref="hr_register_provident_fund"/>
+18 -54
View File
@@ -2,174 +2,138 @@
<odoo>
<data noupdate="1">
<!-- Account Tags -->
<record id="tax_tag_01" model="account.account.tag">
<field name="name">IVA(0%) VENTAS</field>
<record id="tag_iva" model="account.account.tag">
<field name="name">IVA</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_03" model="account.account.tag">
<field name="name">IVA(16%) VENTAS</field>
<record id="tag_isr" model="account.account.tag">
<field name="name">ISR</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_04" model="account.account.tag">
<field name="name">RET IVA FLETES 4%</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_05" model="account.account.tag">
<field name="name">RET IVA ARRENDAMIENTO 10%</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_06" model="account.account.tag">
<field name="name">RET ISR ARRENDAMIENTO 10%</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_07" model="account.account.tag">
<field name="name">RET ISR HONORARIOS 10%</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_08" model="account.account.tag">
<field name="name">RETENCION IVA ARRENDAMIENTO 10.67%</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_09" model="account.account.tag">
<field name="name">RETENCION IVA HONORARIOS 10.67%</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_10" model="account.account.tag">
<field name="name">IVA(0%) COMPRAS</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_12" model="account.account.tag">
<field name="name">IVA(16%) COMPRAS</field>
<record id="tag_ieps" model="account.account.tag">
<field name="name">IEPS</field>
<field name="applicability">taxes</field>
</record>
<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">IVA</field>
<field name="description">IVA(0%)</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="tag_ids" eval="[(6,0,[ref('tag_iva')])]"/>
<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">IVA</field>
<field name="description">IVA(16%)</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="tag_ids" eval="[(6,0,[ref('tag_iva')])]"/>
<field name="tax_group_id" ref="tax_group_iva"/>
</record>
<record id="tax1" model="account.tax.template">
<field name="chart_template_id" ref="mx_coa"/>
<field name="name">RET IVA FLETES 4%</field>
<field name="description">ITAXR_04-OUT</field>
<field name="description">Retención IVA(-4%)</field>
<field name="amount">-4</field>
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<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">
<field name="chart_template_id" ref="mx_coa"/>
<field name="name">RET IVA ARRENDAMIENTO 10%</field>
<field name="description">ITAXR_10-OUT</field>
<field name="description">Retención IVA(-10%)</field>
<field name="amount">-10</field>
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<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">
<field name="chart_template_id" ref="mx_coa"/>
<field name="name">RET ISR ARRENDAMIENTO 10%</field>
<field name="description">ITAXA_10-OUT</field>
<field name="description">Retención ISR(-10%)</field>
<field name="amount">-10</field>
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<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">
<field name="chart_template_id" ref="mx_coa"/>
<field name="name">RET ISR HONORARIOS 10%</field>
<field name="description">ITAXH_10-OUT</field>
<field name="description">Retención ISR(-10%)</field>
<field name="amount">-10</field>
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<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">
<field name="chart_template_id" ref="mx_coa"/>
<field name="name">RETENCION IVA ARRENDAMIENTO 10.67%</field>
<field name="description">ITAX_1067-OUT</field>
<field name="description">Retención IVA(-10.67%)</field>
<field name="amount">-10.67</field>
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<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">
<field name="chart_template_id" ref="mx_coa"/>
<field name="name">RETENCION IVA HONORARIOS 10.67%</field>
<field name="description">ITAX_167-OUT</field>
<field name="description">Retención IVA(-10.67%)</field>
<field name="amount">-10.67</field>
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<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">
<field name="chart_template_id" ref="mx_coa"/>
<field name="name">IVA(0%) COMPRAS</field>
<field name="description">ITAX_010-OUT</field>
<field name="description">IVA(0%)</field>
<field name="amount">0</field>
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<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">
<field name="chart_template_id" ref="mx_coa"/>
<field name="name">IVA(16%) COMPRAS</field>
<field name="description">ITAX_160-OUT</field>
<field name="description">IVA(16%)</field>
<field name="amount">16</field>
<field name="amount_type">percent</field>
<field name="type_tax_use">purchase</field>
<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>
+12 -12
View File
@@ -1,39 +1,39 @@
<?xml version="1.0" ?>
<odoo>
<data noupdate="1">
<record id="account_fiscal_position_personasmoralesdelrgimengeneral0" model="account.fiscal.position">
<record id="account_fiscal_position_personasmoralesdelrgimengeneral0" model="account.fiscal.position.template">
<field name="name">Personas morales del régimen general</field>
</record>
<record id="account_fiscal_position_personasmoralesconfinesnolucrativos0" model="account.fiscal.position">
<record id="account_fiscal_position_personasmoralesconfinesnolucrativos0" model="account.fiscal.position.template">
<field name="name">Personas morales con fines no lucrativos</field>
</record>
<record id="account_fiscal_position_asociacionesreligiosas0" model="account.fiscal.position">
<record id="account_fiscal_position_asociacionesreligiosas0" model="account.fiscal.position.template">
<field name="name">Asociaciones Religiosas</field>
</record>
<record id="account_fiscal_position_personasmoralesdelrgimensimplificado0" model="account.fiscal.position">
<record id="account_fiscal_position_personasmoralesdelrgimensimplificado0" model="account.fiscal.position.template">
<field name="name">Personas morales del régimen simplificado</field>
</record>
<record id="account_fiscal_position_personafisicaconactividadempresarial0" model="account.fiscal.position">
<record id="account_fiscal_position_personafisicaconactividadempresarial0" model="account.fiscal.position.template">
<field name="name">Persona física con actividad empresarial</field>
</record>
<record id="account_fiscal_position_foreign" model="account.fiscal.position">
<record id="account_fiscal_position_foreign" model="account.fiscal.position.template">
<field name="name">Foreign Customer</field>
</record>
<record id="account_fiscal_position0_purchase" model="account.fiscal.position.tax">
<field name="tax_src_id" ref="l10n_mx.1_tax14"/>
<field name="tax_dest_id" ref="l10n_mx.1_tax13"/>
<record id="account_fiscal_position0_purchase" model="account.fiscal.position.tax.template">
<field name="tax_src_id" ref="l10n_mx.tax14"/>
<field name="tax_dest_id" ref="l10n_mx.tax13"/>
<field name="position_id" ref="account_fiscal_position_foreign"/>
</record>
<record id="account_fiscal_position0_sale" model="account.fiscal.position.tax">
<field name="tax_src_id" ref="l10n_mx.1_tax12"/>
<field name="tax_dest_id" ref="l10n_mx.1_tax9"/>
<record id="account_fiscal_position0_sale" model="account.fiscal.position.tax.template">
<field name="tax_src_id" ref="l10n_mx.tax12"/>
<field name="tax_dest_id" ref="l10n_mx.tax9"/>
<field name="position_id" ref="account_fiscal_position_foreign"/>
</record>
+1 -1
View File
@@ -6,7 +6,7 @@
{
'name': 'Netherlands - Accounting',
'version': '3.0',
'category': 'Localization/Account Charts',
'category': 'Localization',
'author': 'ONESTEiN BV',
'website': 'http://www.onestein.eu',
'depends': [
@@ -88,6 +88,11 @@
<field name="tax_src_id" ref="tvad_09"/>
<field name="tax_dest_id" ref="tvatip09"/>
</record>
<record id="afptt_inverse_11" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_2"/>
<field name="tax_src_id" ref="tvad_19"/>
<field name="tax_dest_id" ref="tvatip19"/>
</record>
<record id="afptt_inverse_9" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_2"/>
<field name="tax_src_id" ref="tvad_20"/>
@@ -147,6 +152,11 @@
<field name="tax_src_id" ref="tvad_09"/>
<field name="tax_dest_id" ref="tvati_intrap9"/>
</record>
<record id="afptt_intracom_11" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_3"/>
<field name="tax_src_id" ref="tvad_19"/>
<field name="tax_dest_id" ref="tvati_intrap19"/>
</record>
<record id="afptt_intracom_13" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_3"/>
<field name="tax_src_id" ref="tvad_20"/>
@@ -207,6 +217,11 @@
<field name="tax_src_id" ref="tvad_09"/>
<field name="tax_dest_id" ref="tvati_intrap9"/>
</record>
<record id="afptt_intracoms_11" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_4"/>
<field name="tax_src_id" ref="tvad_19"/>
<field name="tax_dest_id" ref="tvati_intrap19"/>
</record>
<record id="afptt_intracoms_13" model="account.fiscal.position.tax.template">
<field name="position_id" ref="fiscal_position_template_4"/>
<field name="tax_src_id" ref="tvad_20"/>
+98 -4
View File
@@ -135,6 +135,21 @@
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_tva_taxare_inversa_colectat_19" model="account.account.tag">
<field name="name">TVA Taxare Inversa Colectat 19%</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_tva_taxare_inversa_deductibil_19" model="account.account.tag">
<field name="name">TVA Taxare Inversa Deductibil 19%</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_tva_taxare_inversa_19" model="account.account.tag">
<field name="name">TVA Taxare Inversa 19%</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_tva_taxare_inversa_colectat_20" model="account.account.tag">
<field name="name">TVA Taxare Inversa Colectat 20%</field>
<field name="applicability">taxes</field>
@@ -210,6 +225,21 @@
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_tva_intracomunitara_achizitii_colectat_19" model="account.account.tag">
<field name="name">TVA Intracomunitara Achizitii Colectat 19%</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_tva_intracomunitara_achizitii_deductibil_19" model="account.account.tag">
<field name="name">TVA Intracomunitara Achizitii Deductibil 19%</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_tva_intracomunitara_achizitii_19" model="account.account.tag">
<field name="name">TVA Intracomunitara Achizitii 19%</field>
<field name="applicability">taxes</field>
</record>
<record id="tax_tag_tva_intracomunitara_achizitii_colectat_20" model="account.account.tag">
<field name="name">TVA Intracomunitara Achizitii Colectat 20%</field>
<field name="applicability">taxes</field>
@@ -281,7 +311,7 @@
<record id="tvac_19" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="sequence">11</field>
<field name="sequence">9</field>
<field name="name">TVA colectat 19%</field>
<field name="description">TVA colectat 19%</field>
<field name="amount">19</field>
@@ -307,7 +337,7 @@
<record id="tvac_24" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="sequence">9</field>
<field name="sequence">11</field>
<field name="name">TVA colectat 24%</field>
<field name="description">TVA colectat 24%</field>
<field name="amount">24</field>
@@ -359,7 +389,7 @@
<record id="tvad_19" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="sequence">23</field>
<field name="sequence">19</field>
<field name="name">TVA deductibil 19%</field>
<field name="description">TVA deductibil 19%</field>
<field name="amount">19</field>
@@ -385,7 +415,7 @@
<record id="tvad_24" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="sequence">19</field>
<field name="sequence">23</field>
<field name="name">TVA deductibil 24%</field>
<field name="description">TVA deductibil 24%</field>
<field name="amount">24</field>
@@ -506,6 +536,38 @@
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_tva_taxare_inversa_9')])]"/>
</record>
<record id="tvatip19-c" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="name">TVA Taxare Inversa Colectat 19%</field>
<field name="amount">-19</field>
<field name="amount_type">percent</field>
<field name="type_tax_use">none</field>
<field name="refund_account_id" ref="pcg_4427"/>
<field name="account_id" ref="pcg_4427"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_tva_taxare_inversa_colectat_19')])]"/>
</record>
<record id="tvatip19-d" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="name">TVA Taxare Inversa Deductibil 19%</field>
<field name="amount">19</field>
<field name="amount_type">percent</field>
<field name="type_tax_use">none</field>
<field name="refund_account_id" ref="pcg_4426"/>
<field name="account_id" ref="pcg_4426"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_tva_taxare_inversa_deductibil_19')])]"/>
</record>
<record id="tvatip19" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="sequence">32</field>
<field name="name">TVA Taxare Inversa 19%</field>
<field name="description">TVA Taxare Inversa 19%</field>
<field name="amount">1</field>
<field name="amount_type">group</field>
<field name="type_tax_use">purchase</field>
<field name="children_tax_ids" eval="[(6, 0, [ref('tvatip19-d'), ref('tvatip19-c')])]"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_tva_taxare_inversa_19')])]"/>
</record>
<record id="tvatip20-c" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="name">TVA Taxare Inversa Colectat 20%</field>
@@ -730,6 +792,38 @@
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_tva_intracomunitara_achizitii_9')])]"/>
</record>
<record id="tvati_intrap19c" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="name">TVA Intracomunitara Achizitii Colectat 19%</field>
<field name="amount">-19</field>
<field name="amount_type">percent</field>
<field name="type_tax_use">none</field>
<field name="refund_account_id" ref="pcg_4427"/>
<field name="account_id" ref="pcg_4427"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_tva_intracomunitara_achizitii_colectat_19')])]"/>
</record>
<record id="tvati_intrap19d" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="name">TVA Intracomunitara Achizitii Deductibil 19%</field>
<field name="price_include" eval="0"/>
<field name="amount">19</field>
<field name="amount_type">percent</field>
<field name="type_tax_use">none</field>
<field name="refund_account_id" ref="pcg_4426"/>
<field name="account_id" ref="pcg_4426"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_tva_intracomunitara_achizitii_deductibil_19')])]"/>
</record>
<record id="tvati_intrap19" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="sequence">62</field>
<field name="name">TVA Intracomunitara Achizitii 19%</field>
<field name="amount">1</field>
<field name="amount_type">group</field>
<field name="type_tax_use">purchase</field>
<field name="children_tax_ids" eval="[(6, 0, [ref('tvati_intrap19d'), ref('tvati_intrap19c')])]"/>
<field name="tag_ids" eval="[(6,0,[ref('tax_tag_tva_intracomunitara_achizitii_19')])]"/>
</record>
<record id="tvati_intrap20c" model="account.tax.template">
<field name="chart_template_id" ref="ro_chart_template"/>
<field name="name">TVA Intracomunitara Achizitii Colectat 20%</field>
+3 -2
View File
@@ -209,8 +209,9 @@ class Channel(models.Model):
@api.multi
def message_receive_bounce(self, email, partner, mail_id=None):
""" Override bounce management to unsubscribe bouncing addresses """
if partner.message_bounce >= self.MAX_BOUNCE_LIMIT:
self._action_unfollow(partner)
for p in partner:
if p.message_bounce >= self.MAX_BOUNCE_LIMIT:
self._action_unfollow(p)
return super(Channel, self).message_receive_bounce(email, partner, mail_id=mail_id)
@api.multi
+9 -8
View File
@@ -1049,19 +1049,20 @@ class MailThread(models.AbstractModel):
bounce_match = bounce_re.search(email_to)
if bounce_match:
bounced_mail_id, bounced_model, bounced_thread_id = bounce_match.group(1), bounce_match.group(2), int(bounce_match.group(3))
bounced_mail_id, bounced_model, bounced_thread_id = bounce_match.group(1), bounce_match.group(2), bounce_match.group(3)
email_part = next((part for part in message.walk() if part.get_content_type() == 'message/rfc822'), None)
dsn_part = next((part for part in message.walk() if part.get_content_type() == 'message/delivery-status'), None)
partner, partner_address = self.env['res.partner'], False
partners, partner_address = self.env['res.partner'], False
if dsn_part and len(dsn_part.get_payload()) > 1:
dsn = dsn_part.get_payload()[1]
final_recipient_data = tools.decode_message_header(dsn, 'Final-Recipient')
partner_address = final_recipient_data.split(';', 1)[1].strip()
if partner_address:
partner = partner.sudo().search([('email', 'like', partner_address)])
partner.message_receive_bounce(partner_address, partner, mail_id=bounced_mail_id)
partners = partners.sudo().search([('email', 'like', partner_address)])
for partner in partners:
partner.message_receive_bounce(partner_address, partner, mail_id=bounced_mail_id)
mail_message = self.env['mail.message']
if email_part:
@@ -1069,19 +1070,19 @@ class MailThread(models.AbstractModel):
bounced_message_id = tools.mail_header_msgid_re.findall(tools.decode_message_header(email, 'Message-Id'))
mail_message = MailMessage.sudo().search([('message_id', 'in', bounced_message_id)])
if partner and mail_message:
if partners and mail_message:
notifications = self.env['mail.notification'].sudo().search([
('mail_message_id', '=', mail_message.id),
('res_partner_id', '=', partner.id)])
('res_partner_id', 'in', partners.ids)])
notifications.write({
'email_status': 'bounce'
})
if bounced_model in self.env and hasattr(self.env[bounced_model], 'message_receive_bounce') and bounced_thread_id:
self.env[bounced_model].browse(bounced_thread_id).message_receive_bounce(partner_address, partner, mail_id=bounced_mail_id)
self.env[bounced_model].browse(int(bounced_thread_id)).message_receive_bounce(partner_address, partners, mail_id=bounced_mail_id)
_logger.info('Routing mail from %s to %s with Message-Id %s: bounced mail from mail %s, model: %s, thread_id: %s: dest %s (partner %s)',
email_from, email_to, message_id, bounced_mail_id, bounced_model, bounced_thread_id, partner_address, partner.id)
email_from, email_to, message_id, bounced_mail_id, bounced_model, bounced_thread_id, partner_address, partners)
return []
# 0. First check if this is a bounce message or not.
+5 -4
View File
@@ -66,6 +66,7 @@ var Activity = form_common.AbstractField.extend({
this._super.apply(this, arguments);
this.context = this.options.context || {};
this.model = this.view.dataset.model;
this.activities = [];
this.Activity = new Model('mail.activity');
},
@@ -98,7 +99,7 @@ var Activity = form_common.AbstractField.extend({
fetch_and_render_value: function () {
var self = this;
return new Model(this.view.dataset.model)
return new Model(this.model)
.call("read", [[this.get_res_id()], ['activity_ids']])
.then(function (results) {
self.value = results[0]['activity_ids'];
@@ -133,7 +134,7 @@ var Activity = form_common.AbstractField.extend({
target: 'new',
context: _.extend({
default_res_id: this.get_res_id(),
default_res_model: this.view.dataset.model,
default_res_model: this.model,
}, {'mark_done': true}, this.context),
res_id: false,
};
@@ -141,7 +142,7 @@ var Activity = form_common.AbstractField.extend({
on_close: function() {
self.fetch_and_render_value();
self.chatter.refresh_followers();
ChatManager.get_messages({model: self.res_model, res_id: self.get_res_id()});
ChatManager.get_messages({model: self.model, res_id: self.get_res_id()});
},
});
},
@@ -169,7 +170,7 @@ var Activity = form_common.AbstractField.extend({
on_close: function () {
self.fetch_and_render_value();
self.chatter.refresh_followers();
ChatManager.get_messages({model: self.res_model, res_id: self.get_res_id()});
ChatManager.get_messages({model: self.model, res_id: self.get_res_id()});
},
});
},
+3 -1
View File
@@ -1045,7 +1045,9 @@ function init () {
bus.on('notification', null, on_notification);
return session.rpc('/mail/client_action').then(function (result) {
return session.is_bound.then(function(){
return session.rpc('/mail/client_action');
}).then(function (result) {
_.each(result.channel_slots, function (channels) {
_.each(channels, add_channel);
});
+1
View File
@@ -15,6 +15,7 @@
'data/maintenance_data.xml',
'views/maintenance_views.xml',
'views/maintenance_templates.xml',
'data/maintenance_cron.xml',
],
'demo': ['data/maintenance_demo.xml'],
'installable': True,
+67 -23
View File
@@ -144,17 +144,48 @@ class MaintenanceEquipment(models.Model):
maintenance_team_id = fields.Many2one('maintenance.team', string='Maintenance Team')
maintenance_duration = fields.Float(help="Maintenance Duration in minutes and seconds.")
@api.depends('period', 'maintenance_open_count', 'maintenance_ids.request_date')
@api.depends('period', 'maintenance_ids.request_date', 'maintenance_ids.close_date')
def _compute_next_maintenance(self):
for equipment in self:
create_date = equipment.create_date and datetime.strptime(equipment.create_date, DEFAULT_SERVER_DATETIME_FORMAT)
if equipment.period:
next_date = create_date
if equipment.maintenance_ids:
maintenance = equipment.maintenance_ids.sorted(lambda x: x.request_date)[0]
next_date = maintenance.request_date and datetime.strptime(maintenance.request_date, DEFAULT_SERVER_DATE_FORMAT) or create_date
equipment.next_action_date = next_date and (next_date + timedelta(days=equipment.period)).strftime(DEFAULT_SERVER_DATE_FORMAT)
date_now = fields.Date.context_today(self)
for equipment in self.filtered(lambda x: x.period > 0):
next_maintenance_todo = self.env['maintenance.request'].search([
('equipment_id', '=', equipment.id),
('maintenance_type', '=', 'preventive'),
('stage_id.done', '!=', True),
('close_date', '=', False)], order="request_date asc", limit=1)
last_maintenance_done = self.env['maintenance.request'].search([
('equipment_id', '=', equipment.id),
('maintenance_type', '=', 'preventive'),
('stage_id.done', '=', True),
('close_date', '!=', False)], order="close_date desc", limit=1)
if next_maintenance_todo and last_maintenance_done:
next_date = next_maintenance_todo.request_date
date_gap = fields.Date.from_string(next_maintenance_todo.request_date) - fields.Date.from_string(last_maintenance_done.close_date)
# If the gap between the last_maintenance_done and the next_maintenance_todo one is bigger than 2 times the period and next request is in the future
# We use 2 times the period to avoid creation too closed request from a manually one created
if date_gap > timedelta(0) and date_gap > timedelta(days=equipment.period) * 2 and fields.Date.from_string(next_maintenance_todo.request_date) > fields.Date.from_string(date_now):
# If the new date still in the past, we set it for today
if fields.Date.from_string(last_maintenance_done.close_date) + timedelta(days=equipment.period) < fields.Date.from_string(date_now):
next_date = date_now
else:
next_date = fields.Date.to_string(fields.Date.from_string(last_maintenance_done.close_date) + timedelta(days=equipment.period))
elif next_maintenance_todo:
next_date = next_maintenance_todo.request_date
date_gap = fields.Date.from_string(next_maintenance_todo.request_date) - fields.Date.from_string(date_now)
# If next maintenance to do is in the future, and in more than 2 times the period, we insert an new request
# We use 2 times the period to avoid creation too closed request from a manually one created
if date_gap > timedelta(0) and date_gap > timedelta(days=equipment.period) * 2:
next_date = fields.Date.to_string(fields.Date.from_string(date_now)+timedelta(days=equipment.period))
elif last_maintenance_done:
next_date = fields.Date.from_string(last_maintenance_done.close_date)+timedelta(days=equipment.period)
# If when we add the period to the last maintenance done and we still in past, we plan it for today
if next_date < fields.Date.from_string(date_now):
next_date = date_now
else:
next_date = fields.Date.to_string(fields.Date.from_string(date_now) + timedelta(days=equipment.period))
equipment.next_action_date = next_date
@api.one
@api.depends('maintenance_ids.stage_id.done')
def _compute_maintenance_count(self):
@@ -190,18 +221,31 @@ class MaintenanceEquipment(models.Model):
category_ids = categories._search([], order=order, access_rights_uid=SUPERUSER_ID)
return categories.browse(category_ids)
def _create_new_request(self, date):
self.ensure_one()
self.env['maintenance.request'].create({
'name': _('Preventive Maintenance - %s') % self.name,
'request_date': date,
'schedule_date': date,
'category_id': self.category_id.id,
'equipment_id': self.id,
'maintenance_type': 'preventive',
'owner_user_id': self.owner_user_id.id,
'technician_user_id': self.technician_user_id.id,
})
@api.model
def _cron_generate_requests(self):
for equipment in self.search([]):
if equipment.period and equipment.next_action_date == date.today().strftime(DEFAULT_SERVER_DATE_FORMAT):
self.env['maintenance.request'].create({
'name': _('Preventive Maintenance - %s' % equipment.next_action_date),
'request_date': equipment.next_action_date,
'category_id': equipment.category_id.id,
'equipment_id': equipment.id,
'maintenance_type': 'preventive',
})
"""
Generates maintenance request on the next_action_date or today if none exists
"""
for equipment in self.search([('period', '>', 0)]):
next_requests = self.env['maintenance.request'].search([('stage_id.done', '=', False),
('equipment_id', '=', equipment.id),
('maintenance_type', '=', 'preventive'),
('request_date', '=', equipment.next_action_date)])
if not next_requests:
equipment._create_new_request(equipment.next_action_date)
class MaintenanceRequest(models.Model):
_name = 'maintenance.request'
@@ -227,8 +271,8 @@ class MaintenanceRequest(models.Model):
name = fields.Char('Subjects', required=True)
description = fields.Text('Description')
request_date = fields.Date('Request Date', track_visibility='onchange', default=fields.Date.context_today)
request_date = fields.Date('Request Date', track_visibility='onchange', default=fields.Date.context_today,
help="Date requested for the maintenance to happen")
owner_user_id = fields.Many2one('res.users', string='Created by', default=lambda s: s.env.uid)
category_id = fields.Many2one('maintenance.equipment.category', related='equipment_id.category_id', string='Category', store=True, readonly=True)
equipment_id = fields.Many2one('maintenance.equipment', string='Equipment', index=True)
@@ -237,13 +281,13 @@ class MaintenanceRequest(models.Model):
group_expand='_read_group_stage_ids', default=_default_stage)
priority = fields.Selection([('0', 'Very Low'), ('1', 'Low'), ('2', 'Normal'), ('3', 'High')], string='Priority')
color = fields.Integer('Color Index')
close_date = fields.Date('Close Date')
close_date = fields.Date('Close Date', help="Date the maintenance was finished. ")
kanban_state = fields.Selection([('normal', 'In Progress'), ('blocked', 'Blocked'), ('done', 'Ready for next stage')],
string='Kanban State', required=True, default='normal', track_visibility='onchange')
# active = fields.Boolean(default=True, help="Set active to false to hide the maintenance request without deleting it.")
archive = fields.Boolean(default=False, help="Set archive to true to hide the maintenance request without deleting it.")
maintenance_type = fields.Selection([('corrective', 'Corrective'), ('preventive', 'Preventive')], string='Maintenance Type', default="corrective")
schedule_date = fields.Datetime('Scheduled Date')
schedule_date = fields.Datetime('Scheduled Date', help="Date the maintenance team plans the maintenance. It should not differ much from the Request Date. ")
maintenance_team_id = fields.Many2one('maintenance.team', string='Team', required=True, default=_get_default_team_id)
duration = fields.Float(help="Duration in minutes and seconds.")
+3 -3
View File
@@ -12,14 +12,14 @@
<record id="equipment_request_rule_user" model="ir.rule">
<field name="name">Users are allowed to access their own maintenance requests</field>
<field name="model_id" ref="model_maintenance_request"/>
<field name="domain_force">['|', ('owner_user_id.id', '=', user.id), ('technician_user_id.id', '=', user.id)]</field>
<field name="domain_force">['|', ('message_partner_ids', 'in', [user.partner_id.id]), ('technician_user_id.id', '=', user.id)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
<record id="equipment_rule_user" model="ir.rule">
<field name="name">Users are allowed to access their own equipments</field>
<field name="name">Users are allowed to access equipments they follow</field>
<field name="model_id" ref="model_maintenance_equipment"/>
<field name="domain_force">[('owner_user_id.id', '=', user.id)]</field>
<field name="domain_force">[('message_partner_ids', 'in', [user.partner_id.id])]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
+32 -2
View File
@@ -4,7 +4,8 @@
import time
from odoo.tests.common import TransactionCase
from dateutil import relativedelta
import datetime
class TestEquipment(TransactionCase):
""" Test used to check that when doing equipment/maintenance_request/equipment_category creation."""
@@ -36,7 +37,7 @@ class TestEquipment(TransactionCase):
groups_id=[(6, 0, [res_manager.id])]
))
def test_equipment_request_category(self):
def test_10_equipment_request_category(self):
# Create a new equipment
equipment_01 = self.equipment.sudo(self.manager).create({
@@ -75,3 +76,32 @@ class TestEquipment(TransactionCase):
# I check that maintenance request is in the "In Progress" stage
self.assertEquals(maintenance_request_01.stage_id.id, self.ref('maintenance.stage_1'))
def test_20_cron(self):
""" Check the cron creates the necessary preventive maintenance requests"""
equipment_cron = self.equipment.create({
'name': 'High Maintenance Monitor because of Color Calibration',
'category_id': self.ref('maintenance.equipment_monitor'),
'technician_user_id': self.ref('base.user_root'),
'owner_user_id': self.user.id,
'assign_date': time.strftime('%Y-%m-%d'),
'period': 7,
'color': 3,
})
maintenance_request_cron = self.maintenance_request.create({
'name': 'Need a special calibration',
'technician_user_id': self.user.id,
'request_date': (datetime.datetime.now() + relativedelta.relativedelta(days=7)).strftime('%Y-%m-%d'),
'maintenance_type': 'preventive',
'owner_user_id': self.user.id,
'equipment_id': equipment_cron.id,
'color': 7,
'stage_id': self.ref('maintenance.stage_0'),
'maintenance_team_id': self.ref('maintenance.equipment_team_maintenance')
})
self.env['maintenance.equipment']._cron_generate_requests()
# As it is generating the requests for one month in advance, we should have 4 requests in total
tot_requests = self.maintenance_request.search([('equipment_id', '=', equipment_cron.id)])
self.assertEqual(len(tot_requests), 1, 'The cron should have generated just 1 request for the High Maintenance Monitor.')
@@ -78,7 +78,7 @@
<field name="owner_user_id" string="Requested By"/>
<field name="equipment_id" context="{'default_category_id':category_id}"/>
<field name="category_id" groups="maintenance.group_equipment_manager"/>
<field name="request_date" groups="base.group_no_one"/>
<field name="request_date"/>
<field name="close_date" groups="base.group_no_one"/>
<field name="archive" invisible="1"/>
<field name="maintenance_type" widget="radio"/>
@@ -416,7 +416,7 @@
<div class="oe_kanban_footer_left mt4 label label-danger" t-if="record.maintenance_open_count.raw_value" >
<t t-raw="record.maintenance_open_count.raw_value"/> Request
</div>
<div class="oe_kanban_footer_left mt4 label label-default" t-if="record.maintenance_open_count.raw_value == 0 and record.next_action_date.raw_value" >
<div class="oe_kanban_footer_left mt4 label label-default" t-if="record.next_action_date.raw_value" >
<t t-raw="moment(record.next_action_date.raw_value).format('MMMM Do')"/>
</div>
</div>
+1 -1
View File
@@ -10,7 +10,7 @@ class TestMassMailing(models.TransientModel):
email_to = fields.Char(string='Recipients', required=True,
help='Comma-separated list of email addresses.', default=lambda self: self.env['mail.message']._get_default_from())
mass_mailing_id = fields.Many2one('mail.mass_mailing', string='Mailing', required=True)
mass_mailing_id = fields.Many2one('mail.mass_mailing', string='Mailing', required=True, ondelete='cascade')
@api.multi
def send_mail_test(self):
+5
View File
@@ -4,6 +4,7 @@
from odoo import api, fields, models, _
from odoo.addons import decimal_precision as dp
from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_round
class MrpBom(models.Model):
@@ -138,6 +139,10 @@ class MrpBom(models.Model):
templates_done |= current_line.product_id.product_tmpl_id
boms_done.append((bom, {'qty': converted_line_quantity, 'product': current_product, 'original_qty': quantity, 'parent_line': current_line}))
else:
# We round up here because the user expects that if he has to consume a little more, the whole UOM unit
# should be consumed.
rounding = current_line.product_uom_id.rounding
line_quantity = float_round(line_quantity, precision_rounding=rounding, rounding_method='UP')
lines_done.append((current_line, {'qty': line_quantity, 'product': current_product, 'original_qty': quantity, 'parent_line': parent_line}))
return boms_done, lines_done
+1
View File
@@ -323,6 +323,7 @@ class MrpProduction(models.Model):
data = {
'name': self.name,
'date': self.date_planned_start,
'date_expected': self.date_planned_start,
'bom_line_id': bom_line.id,
'product_id': bom_line.product_id.id,
'product_uom_qty': quantity,
+3 -2
View File
@@ -6,7 +6,7 @@ from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import float_compare
from odoo.tools import float_compare, float_round
from odoo.addons import decimal_precision as dp
@@ -275,7 +275,8 @@ class MrpWorkorder(models.Model):
raw_moves = self.move_raw_ids.filtered(lambda x: (x.has_tracking == 'none') and (x.state not in ('done', 'cancel')) and x.bom_line_id)
for move in raw_moves:
if move.unit_factor:
move.quantity_done += self.qty_producing * move.unit_factor
rounding = move.product_uom.rounding
move.quantity_done += float_round(self.qty_producing * move.unit_factor, precision_rounding=rounding)
# Transfer quantities from temporary to final move lots or make them final
for move_lot in self.active_move_lot_ids:
+11 -5
View File
@@ -3,7 +3,7 @@
from odoo import api, exceptions, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import float_compare
from odoo.tools import float_compare, float_round
from odoo.addons import decimal_precision as dp
@@ -81,14 +81,14 @@ class StockMove(models.Model):
store=True,
help='Technical Field to order moves') # TDE: what ?
@api.multi
@api.depends('state', 'product_uom_qty', 'reserved_availability')
def _qty_available(self):
for move in self:
# For consumables, state is available so availability = qty to do
if move.state == 'assigned':
move.quantity_available = move.product_uom_qty
else:
move.quantity_available = move.reserved_availability
elif move.product_id.uom_id and move.product_uom:
move.quantity_available = move.product_id.uom_id._compute_quantity(move.reserved_availability, move.product_uom)
@api.multi
@api.depends('move_lot_ids', 'move_lot_ids.quantity_done', 'quantity_done_store')
@@ -225,8 +225,14 @@ class StockMove(models.Model):
moves_to_unreserve = self.env['stock.move']
# Create extra moves where necessary
for move in moves:
# Here, the `quantity_done` was already rounded to the product UOM by the `do_produce` wizard. However,
# it is possible that the user changed the value before posting the inventory by a value that should be
# rounded according to the move's UOM. In this specific case, we chose to round up the value, because it
# is what is expected by the user (if i consumed/produced a little more, the whole UOM unit should be
# consumed/produced and the moves are split correctly).
rounding = move.product_uom.rounding
if float_compare(move.quantity_done, 0.0, precision_rounding=rounding) <= 0:
move.quantity_done = float_round(move.quantity_done, precision_rounding=rounding, rounding_method ='UP')
if move.quantity_done <= 0:
continue
moves_todo |= move
moves_todo |= move._create_extra_move()
+32
View File
@@ -427,3 +427,35 @@ class TestMrpOrder(TestMrpCommon):
# check the consumed quants of the newly produced quant
self.assertEquals(sum(second_move.quant_ids.mapped('consumed_quant_ids').mapped('qty')), 2)
def test_rounding(self):
""" In previous versions we had rounding and efficiency fields. We check if we can still do the same, but with only the rounding on the UoM"""
self.product_6.uom_id.rounding = 1.0
bom_eff = self.env['mrp.bom'].create({'product_id': self.product_6.id,
'product_tmpl_id': self.product_6.product_tmpl_id.id,
'product_qty': 1,
'product_uom_id': self.product_6.uom_id.id,
'type': 'normal',
'bom_line_ids': [
(0, 0, {'product_id': self.product_2.id, 'product_qty': 2.03}),
(0, 0, {'product_id': self.product_8.id, 'product_qty': 4.16})
]})
production = self.env['mrp.production'].create({'name': 'MO efficiency test',
'product_id': self.product_6.id,
'product_qty': 20,
'bom_id': bom_eff.id,
'product_uom_id': self.product_6.uom_id.id,})
#Check the production order has the right quantities
self.assertEqual(production.move_raw_ids[0].product_qty, 41, 'The quantity should be rounded up')
self.assertEqual(production.move_raw_ids[1].product_qty, 84, 'The quantity should be rounded up')
# produce product
produce_wizard = self.env['mrp.product.produce'].with_context({
'active_id': production.id,
'active_ids': [production.id],
}).create({
'product_qty': 8,
})
produce_wizard.do_produce()
self.assertEqual(production.move_raw_ids[0].quantity_done, 16, 'Should use half-up rounding when producing')
self.assertEqual(production.move_raw_ids[1].quantity_done, 34, 'Should use half-up rounding when producing')
+7 -4
View File
@@ -6,7 +6,7 @@ from datetime import datetime
from odoo import api, fields, models, _
from odoo.addons import decimal_precision as dp
from odoo.exceptions import UserError
from odoo.tools import float_compare
from odoo.tools import float_compare, float_round
class MrpProductProduce(models.TransientModel):
_name = "mrp.product.produce"
@@ -80,13 +80,16 @@ class MrpProductProduce(models.TransientModel):
raise UserError(_('You should at least produce some quantity'))
for move in moves.filtered(lambda x: x.product_id.tracking == 'none' and x.state not in ('done', 'cancel')):
if move.unit_factor:
move.quantity_done_store += quantity * move.unit_factor
rounding = move.product_uom.rounding
move.quantity_done_store += float_round(quantity * move.unit_factor, precision_rounding=rounding)
moves = self.production_id.move_finished_ids.filtered(lambda x: x.product_id.tracking == 'none' and x.state not in ('done', 'cancel'))
for move in moves:
rounding = move.product_uom.rounding
if move.product_id.id == self.production_id.product_id.id:
move.quantity_done_store += quantity
move.quantity_done_store += float_round(quantity, precision_rounding=rounding)
elif move.unit_factor:
move.quantity_done_store += quantity * move.unit_factor
# byproducts handling
move.quantity_done_store += float_round(quantity * move.unit_factor, precision_rounding=rounding)
self.check_finished_move_lots()
if self.production_id.state == 'confirmed':
self.production_id.write({
@@ -17,7 +17,7 @@
<field name="product_uom_id" readonly="1"/>
</div>
<field name="product_tracking" invisible="1"/>
<field name="lot_id" attrs="{'invisible': [('product_tracking', '=', 'none')]}" domain="[('product_id','=',product_id)]" context="{'default_product_id': product_id}"/>
<field name="lot_id" required="1" attrs="{'invisible': [('product_tracking', '=', 'none')]}" domain="[('product_id','=',product_id)]" context="{'default_product_id': product_id}"/>
</group>
</group>
<group>
+1 -1
View File
@@ -41,7 +41,7 @@ class AccountPayment(models.Model):
if self.payment_token_id.acquirer_id.auto_confirm == 'authorize':
raise ValidationError('This feature is not available for payment acquirers set to the "Authorize" mode.\n'
'Please use a token from another provider than %s.' % self.payment_token_id.acquirer_id.name)
reference = "PAYMENT-%s-%s" % (self.id, datetime.datetime.now().strftime('%y%m%d_%H%M%S'))
reference = "P-%s-%s" % (self.id, datetime.datetime.now().strftime('%y%m%d_%H%M%S'))
tx = self.env['payment.transaction'].create({
'amount': self.amount,
'acquirer_id': self.payment_token_id.acquirer_id.id,
@@ -147,6 +147,17 @@ class TxAuthorize(models.Model):
# FORM RELATED METHODS
# --------------------------------------------------
@api.model
def create(self, vals):
# The reference is used in the Authorize form to fill a field (invoiceNumber) which is
# limited to 20 characters. We truncate the reference now, since it will be reused at
# payment validation to find back the transaction.
if 'reference' in vals and 'acquirer_id' in vals:
acquier = self.env['payment.acquirer'].browse(vals['acquirer_id'])
if acquier.provider == 'authorize':
vals['reference'] = vals.get('reference', '')[:20]
return super(TxAuthorize, self).create(vals)
@api.model
def _authorize_form_get_tx_from_data(self, data):
""" Given a data dict coming from authorize, verify it and find the related
+9
View File
@@ -198,6 +198,7 @@ class PosOrder(models.Model):
grouped_data = {}
have_to_group_by = session and session.config_id.group_by or False
rounding_method = session and session.config_id.company_id.tax_calculation_rounding_method
for order in self.filtered(lambda o: not o.account_move or order.state == 'paid'):
current_company = order.sale_journal.company_id
@@ -296,6 +297,14 @@ class PosOrder(models.Model):
'partner_id': partner_id
})
# round tax lines per order
if rounding_method == 'round_globally':
for group_key, group_value in grouped_data.iteritems():
if group_key[0] == 'tax':
for line in group_value:
line['credit'] = cur.round(line['credit'])
line['debit'] = cur.round(line['debit'])
# counterpart
insert_data('counter_part', {
'name': _("Trade Receivables"), # order.name,
+10 -4
View File
@@ -278,13 +278,19 @@ var DebugWidget = PosBaseWidget.extend({
});
this.$('.button.export_unpaid_orders').click(function(){
self.gui.download_file(self.pos.export_unpaid_orders(),
"unpaid_orders_" + (new Date()).toUTCString().replace(/\ /g,'_') + '.json');
self.gui.prepare_download_link(
self.pos.export_unpaid_orders(),
_t("unpaid orders") + ' ' + moment().format('YYYY-MM-DD-HH-mm-ss') + '.json',
".export_unpaid_orders", ".download_unpaid_orders"
);
});
this.$('.button.export_paid_orders').click(function() {
self.gui.download_file(self.pos.export_paid_orders(),
"paid_orders_" + (new Date()).toUTCString().replace(/\ /g,'_') + '.json');
self.gui.prepare_download_link(
self.pos.export_paid_orders(),
_t("paid orders") + ' ' + moment().format('YYYY-MM-DD-HH-mm-ss') + '.json',
".export_paid_orders", ".download_paid_orders"
);
});
this.$('.button.import_orders input').on('change', function(event) {
+29 -7
View File
@@ -362,7 +362,34 @@ var Gui = core.Class.extend({
// if 'contents' is not a string, it is converted into
// a JSON representation of the contents.
// TODO: remove me in master: deprecated in favor of prepare_download_link
// this method is kept for backward compatibility but is likely not going
// to work as many browsers do to not accept fake click events on links
download_file: function(contents, name) {
href_params = this.prepare_file_blob(contents,name);
var evt = document.createEvent("HTMLEvents");
evt.initEvent("click");
$("<a>",href_params).get(0).dispatchEvent(evt);
},
prepare_download_link: function(contents, filename, src, target) {
var href_params = this.prepare_file_blob(contents, filename);
$(target).parent().attr(href_params);
$(src).addClass('oe_hidden');
$(target).removeClass('oe_hidden');
// hide again after click
$(target).click(function() {
$(src).removeClass('oe_hidden');
$(this).addClass('oe_hidden');
});
},
prepare_file_blob: function(contents, name) {
var URL = window.URL || window.webkitURL;
if (typeof contents !== 'string') {
@@ -371,13 +398,8 @@ var Gui = core.Class.extend({
var blob = new Blob([contents]);
var evt = document.createEvent("HTMLEvents");
evt.initEvent("click");
$("<a>",{
download: name || 'document.txt',
href: URL.createObjectURL(blob),
}).get(0).dispatchEvent(evt);
return {download: name || 'document.txt',
href: URL.createObjectURL(blob),}
},
/* ---- Gui: EMAILS ---- */
+9 -2
View File
@@ -675,6 +675,8 @@ exports.PosModel = Backbone.Model.extend({
flushed.always(function(ids){
pushed.resolve();
});
return flushed;
});
return pushed;
},
@@ -775,6 +777,11 @@ exports.PosModel = Backbone.Model.extend({
var self = this;
var timeout = typeof options.timeout === 'number' ? options.timeout : 7500 * orders.length;
// Keep the order ids that are about to be sent to the
// backend. In between create_from_ui and the success callback
// new orders may have been added to it.
var order_ids_to_sync = _.pluck(orders, 'id');
// we try to send the order. shadow prevents a spinner if it takes too long. (unless we are sending an invoice,
// then we want to notify the user that we are waiting on something )
var posOrderModel = new Model('pos.order');
@@ -789,8 +796,8 @@ exports.PosModel = Backbone.Model.extend({
timeout: timeout
}
).then(function (server_ids) {
_.each(orders, function (order) {
self.db.remove_order(order.id);
_.each(order_ids_to_sync, function (order_id) {
self.db.remove_order(order_id);
});
self.set('failed',false);
return server_ids;
+3 -1
View File
@@ -105,7 +105,9 @@ var ErrorTracebackPopupWidget = ErrorPopupWidget.extend({
this._super(opts);
this.$('.download').off('click').click(function(){
self.gui.download_file(self.options.body,'traceback.txt');
self.gui.prepare_download_link(self.options.body,
_t('error') + ' ' + moment().format('YYYY-MM-DD-HH-mm-ss') + '.txt',
'.download', '.download_error_file');
});
this.$('.email').off('click').click(function(){
+6 -1
View File
@@ -336,7 +336,7 @@
</div>
<div class='client-detail'>
<span class='label'>Country</span>
<select class='detail client-address-country' name='country_id'>
<select class='detail client-address-country needsclick' name='country_id'>
<option value=''>None</option>
<t t-foreach='widget.pos.countries' t-as='country'>
<option t-att-value='country.id' t-att-selected="partner.country_id ? ((country.id === partner.country_id[0]) ? true : undefined) : undefined">
@@ -975,6 +975,9 @@
<div class="button cancel">
Ok
</div>
<a><div class="button icon download_error_file oe_hidden">
<i class="fa fa-arrow-down"></i>
</div></a>
<div class="button icon download">
<i class="fa fa-download"></i>
</div>
@@ -1308,7 +1311,9 @@
<li class="button delete_orders">Delete Paid Orders</li>
<li class="button delete_unpaid_orders">Delete Unpaid Orders</li>
<li class="button export_paid_orders">Export Paid Orders</li>
<a><li class="button download_paid_orders oe_hidden">Download Paid Orders</li></a>
<li class="button export_unpaid_orders">Export Unpaid Orders</li>
<a><li class="button download_unpaid_orders oe_hidden">Download Unpaid Orders</li></a>
<li class="button import_orders" style="position:relative">
Import Orders
<input type="file" style="opacity:0;position:absolute;top:0;left:0;right:0;bottom:0;margin:0;cursor:pointer"/>
@@ -4,7 +4,7 @@
<field name="name">report.pos.order.pivot</field>
<field name="model">report.pos.order</field>
<field name="arch" type="xml">
<pivot string="Point of Sale Analysis">
<pivot string="Point of Sale Analysis" disable_linking="True">
<field name="product_categ_id" type="row"/>
<field name="date" interval="month" type="col"/>
<field name="product_qty" type="measure"/>
+1 -1
View File
@@ -84,7 +84,7 @@ class ProductUoM(models.Model):
if misc_category:
values['category_id'] = misc_category.id
else:
values['category_id'] = EnglishUoMCateg.name_create('Unsorted/Imported Units').id
values['category_id'] = EnglishUoMCateg.name_create('Unsorted/Imported Units')[0]
new_uom = self.create(values)
return new_uom.name_get()[0]
+2
View File
@@ -44,6 +44,7 @@
<group>
<group name="group_general">
<field name="type"/>
<field name="categ_id" string="Internal Category"/>
</group>
<group name="group_standard_price">
<field name="list_price" widget='monetary' options="{'currency_field': 'currency_id'}"/>
@@ -310,6 +311,7 @@
<field name="item_ids" position="replace">
<field name="pricelist_item_ids" widget="many2many">
<tree string="Pricelist Items">
<field name="pricelist_id"/>
<field name="name" string="Applicable On"/>
<field name="min_quantity"/>
<field name="date_start"/>
+25 -25
View File
@@ -97,7 +97,7 @@
<data noupdate="1">
<record id="project_project_data" model="project.project">
<record forcecreate="False" id="project_project_data" model="project.project">
<field name="name">Start here to discover Odoo</field>
<field name="privacy_visibility">followers</field>
<field name="user_id" ref="base.user_root"/>
@@ -111,25 +111,25 @@
<field name="alias_parent_model_id" ref="model_project_project"/>
</record>
<record id="project_stage_data_0" model="project.task.type">
<record forcecreate="False" id="project_stage_data_0" model="project.task.type">
<field name="sequence">1</field>
<field name="name">New</field>
<field name="project_ids" eval="[(4, ref('project_project_data'))]"/>
</record>
<record id="project_stage_data_1" model="project.task.type">
<record forcecreate="False" id="project_stage_data_1" model="project.task.type">
<field name="sequence">2</field>
<field name="name">Basic</field>
<field name="project_ids" eval="[(4, ref('project_project_data'))]"/>
</record>
<record id="project_stage_data_2" model="project.task.type">
<record forcecreate="False" id="project_stage_data_2" model="project.task.type">
<field name="sequence">3</field>
<field name="name">Advanced</field>
<field name="project_ids" eval="[(4, ref('project_project_data'))]"/>
</record>
<record id="project_task_data_0" model="project.task">
<record forcecreate="False" id="project_task_data_0" model="project.task">
<field name="sequence">1</field>
<field name="user_id" ref="base.user_root"/>
<field name="priority">0</field>
@@ -141,7 +141,7 @@
<field name="stage_id" ref="project_stage_data_0"/>
</record>
<record id="project_task_data_1" model="project.task">
<record forcecreate="False" id="project_task_data_1" model="project.task">
<field name="sequence">2</field>
<field name="user_id" ref="base.user_root"/>
<field name="priority">0</field>
@@ -152,7 +152,7 @@
<field name="stage_id" ref="project_stage_data_0"/>
</record>
<record id="project_task_data_5" model="project.task">
<record forcecreate="False" id="project_task_data_5" model="project.task">
<field name="sequence">3</field>
<field name="user_id" ref="base.user_root"/>
<field name="priority">0</field>
@@ -163,7 +163,7 @@
<field name="stage_id" ref="project_stage_data_0"/>
</record>
<record id="project_task_data_2" model="project.task">
<record forcecreate="False" id="project_task_data_2" model="project.task">
<field name="sequence">4</field>
<field name="user_id" ref="base.user_root"/>
<field name="priority">0</field>
@@ -174,7 +174,7 @@
<field name="stage_id" ref="project_stage_data_0"/>
</record>
<record id="project_task_data_4" model="project.task">
<record forcecreate="False" id="project_task_data_4" model="project.task">
<field name="user_id" ref="base.user_root"/>
<field name="priority">0</field>
<field name="project_id" ref="project.project_project_data"/>
@@ -184,7 +184,7 @@
<field name="stage_id" ref="project_stage_data_2"/>
</record>
<record id="msg_task_4" model="mail.message">
<record forcecreate="False" id="msg_task_4" model="mail.message">
<field name="subject">Converse with your customers and colleagues</field>
<field name="model">project.task</field>
<field name="author_id" ref="base.partner_root"/>
@@ -194,12 +194,12 @@
<field name="subtype_id" ref="mail.mt_comment"/>
</record>
<record id="project_tag_data" model="project.tags">
<record forcecreate="False" id="project_tag_data" model="project.tags">
<field name="name">NeedAssistance</field>
<field name="color" eval="5"/>
</record>
<record id="project_task_data_6" model="project.task">
<record forcecreate="False" id="project_task_data_6" model="project.task">
<field name="sequence">3</field>
<field name="user_id" ref="base.user_root"/>
<field name="priority">0</field>
@@ -212,7 +212,7 @@
<field name="tag_ids" eval="[(6,0,[ref('project.project_tag_data')])]"/>
</record>
<record id="project_task_data_12" model="project.task">
<record forcecreate="False" id="project_task_data_12" model="project.task">
<field name="sequence">4</field>
<field name="user_id" ref="base.user_root"/>
<field name="priority">0</field>
@@ -224,7 +224,7 @@
<field name="stage_id" ref="project_stage_data_1"/>
</record>
<record id="project_task_data_13" model="project.task">
<record forcecreate="False" id="project_task_data_13" model="project.task">
<field name="sequence">5</field>
<field name="user_id" ref="base.user_root"/>
<field name="priority">0</field>
@@ -235,7 +235,7 @@
<field name="stage_id" ref="project_stage_data_1"/>
</record>
<record id="project_task_data_8" model="project.task">
<record forcecreate="False" id="project_task_data_8" model="project.task">
<field name="user_id" ref="base.user_root"/>
<field name="priority">0</field>
<field name="project_id" ref="project.project_project_data"/>
@@ -245,7 +245,7 @@
<field name="description">Click on the bar in the upper right corner</field>
</record>
<record id="msg_task_data_8_attach" model="ir.attachment">
<record forcecreate="False" id="msg_task_data_8_attach" model="ir.attachment">
<field name="name">planner_icon.png</field>
<field name="datas_fname">planner_icon.png</field>
<field name="datas" type="base64" file="project/static/src/img/planner_icon.png"></field>
@@ -253,7 +253,7 @@
<field name="res_id" ref="project_task_data_8"/>
</record>
<record id="msg_task_data_8" model="mail.message">
<record forcecreate="False" id="msg_task_data_8" model="mail.message">
<field name="subject">How to open the implementation guide ?</field>
<field name="model">project.task</field>
<field name="res_id" ref="project_task_data_8"/>
@@ -264,11 +264,11 @@
<field name="attachment_ids" eval="[(6, 0, [ref('msg_task_data_8_attach')])]"/>
</record>
<record id="project_task_data_8" model="project.task">
<record forcecreate="False" id="project_task_data_8" model="project.task">
<field name="displayed_image_id" ref="msg_task_data_8_attach"/>
</record>
<record id="project_task_data_7" model="project.task">
<record forcecreate="False" id="project_task_data_7" model="project.task">
<field name="user_id" ref="base.user_root"/>
<field name="priority">0</field>
<field name="project_id" ref="project.project_project_data"/>
@@ -278,7 +278,7 @@
<field name="description">Click on the gear icon on the column, to archive the stage, with all the tasks in it. You can also archive one card only, by clicking on the button in the task form view.</field>
</record>
<record id="project_task_data_9" model="project.task">
<record forcecreate="False" id="project_task_data_9" model="project.task">
<field name="user_id" ref="base.user_root"/>
<field name="priority">0</field>
<field name="project_id" ref="project.project_project_data"/>
@@ -288,7 +288,7 @@
<field name="description">Click on the last column to create a stage. The name depends on the process. For example, in a customer service process, a stage name may be 'Backlog', 'Waiting Customer Feedback' or 'Done'.</field>
</record>
<record id="project_task_data_11" model="project.task">
<record forcecreate="False" id="project_task_data_11" model="project.task">
<field name="user_id" ref="base.user_root"/>
<field name="priority">0</field>
<field name="project_id" ref="project.project_project_data"/>
@@ -298,7 +298,7 @@
<field name="date_deadline" eval="datetime.now()+timedelta(days=30)"/>
</record>
<record id="project_task_data_14" model="project.task">
<record forcecreate="False" id="project_task_data_14" model="project.task">
<field name="user_id" ref="base.user_root"/>
<field name="priority">0</field>
<field name="project_id" ref="project.project_project_data"/>
@@ -308,7 +308,7 @@
<field name="description">In the chatter, sending an email with an attachment will display the picture on the card. When there are several image attachments, you can choose which one you want to display.</field>
</record>
<record id="msg_task_data_14_attach" model="ir.attachment">
<record forcecreate="False" id="msg_task_data_14_attach" model="ir.attachment">
<field name="name">bird.jpg</field>
<field name="datas_fname">bird.jpg</field>
<field name="datas" type="base64" file="project/static/src/img/bird.jpg"></field>
@@ -317,7 +317,7 @@
</record>
<record id="msg_task_data_14" model="mail.message">
<record forcecreate="False" id="msg_task_data_14" model="mail.message">
<field name="subject">How to open the planner?</field>
<field name="model">project.task</field>
<field name="res_id" ref="project_task_data_14"/>
@@ -327,7 +327,7 @@
<field name="attachment_ids" eval="[(6, 0, [ref('msg_task_data_14_attach')])]"/>
</record>
<record id="project_task_data_14" model="project.task">
<record forcecreate="False" id="project_task_data_14" model="project.task">
<field name="displayed_image_id" ref="msg_task_data_14_attach"/>
</record>
+44 -44
View File
@@ -124,7 +124,7 @@
<div class="panel panel-success">
<div class="panel-heading">
<span class="panel-title">
<span class="fa fa-check-square-o"/>
<span class="fa fa-check-square-o"></span>
<strong> Tasks</strong>
</span>
</div>
@@ -142,7 +142,7 @@
<div class="panel panel-success">
<div class="panel-heading">
<span class="panel-title">
<span class="fa fa-exclamation-circle"/>
<span class="fa fa-exclamation-circle"></span>
<strong> Issues</strong>
</span>
</div>
@@ -159,7 +159,7 @@
<div class="panel panel-success">
<div class="panel-heading">
<span class="panel-title">
<span class="fa fa-clock-o"/>
<span class="fa fa-clock-o"></span>
<strong> Timesheets</strong>
</span>
</div>
@@ -225,8 +225,8 @@
</div>
<table class="table">
<thead>
<th><h4><span class="fa fa-thumbs-o-up"/> The <strong>Right Way</strong> to use projects:</h4></th>
<th><h4><span class="fa fa-thumbs-o-down"/> The <strong> Wrong Way</strong> to use projects:</h4></th>
<th><h4><span class="fa fa-thumbs-o-up"></span> The <strong>Right Way</strong> to use projects:</h4></th>
<th><h4><span class="fa fa-thumbs-o-down"></span> The <strong> Wrong Way</strong> to use projects:</h4></th>
</thead>
<tbody>
<tr>
@@ -246,8 +246,8 @@
<div class="well well-lg">
<strong>Need help with defining your Projects? Contact us now.</strong><br/>
<a href="https://www.odoo.com/page/live-chat" target="_blank" style="margin-right:10px"><span class="fa fa-comment-o"/> Website Live Chat</a>
<a href="https://www.odoo.com/help"><span class="fa fa-envelope-o"/> Email Our Project Expert</a>
<a href="https://www.odoo.com/page/live-chat" target="_blank" style="margin-right:10px"><span class="fa fa-comment-o"></span> Website Live Chat</a>
<a href="https://www.odoo.com/help"><span class="fa fa-envelope-o"></span> Email Our Project Expert</a>
</div>
</t>
@@ -360,8 +360,8 @@
<div class="col-md-6 col-xs-12">
<div class="well well-lg">
<strong>Need help to structure your projects?</strong><br/>
<a href="https://www.odoo.com/page/live-chat" target="_blank" style="margin-right:10px"><span class="fa fa-comment-o"/> Website Live Chat on</a><br/>
<a href="https://www.odoo.com/help"><span class="fa fa-envelope-o"/> Email Our Project Expert</a>
<a href="https://www.odoo.com/page/live-chat" target="_blank" style="margin-right:10px"><span class="fa fa-comment-o"></span> Website Live Chat on</a><br/>
<a href="https://www.odoo.com/help"><span class="fa fa-envelope-o"></span> Email Our Project Expert</a>
</div>
</div>
</div>
@@ -379,7 +379,7 @@
<section>
<div class="o_planner_paragraphs">
<div class="row">
<div class="col-sm-1 col-md-2 icon"><span data-icon="&#xe02c;" /></div>
<div class="col-sm-1 col-md-2 icon"><span data-icon="&#xe02c;"></span></div>
<div class="col-sm-10 col-md-10">
<p><strong>Define a Naming Convention</strong><br/>
Add keywords in the 'Task title' field, for example [Customer name] or [Website]. This will help you navigate and search through the dozens of tasks in your project.
@@ -387,7 +387,7 @@
</div>
</div>
<div class="row">
<div class="col-sm-1 col-md-2 icon"><span data-icon="&#xe075;" /></div>
<div class="col-sm-1 col-md-2 icon"><span data-icon="&#xe075;"></span></div>
<div class="col-sm-11 col-md-10">
<p><strong>Add a Deadline</strong><br/>
The deadline will help you determine if a task or issue is progressing as expected and to anticipate its next update.
@@ -395,7 +395,7 @@
</div>
</div>
<div class="row">
<div class="col-sm-1 col-md-2 icon"><span data-icon="&#xe055;" /></div>
<div class="col-sm-1 col-md-2 icon"><span data-icon="&#xe055;"></span></div>
<div class="col-sm-11 col-md-10">
<p><strong>Use Tags</strong><br/>
Tags are complementary to your project stages: they can work as a second level categorization which is very useful if you have a lot of Tasks or Issues to manage. Also, it becomes very easy to find Tasks or Issues by typing the Tag into the main Search bar.
@@ -403,7 +403,7 @@
</div>
</div>
<div class="row">
<div class="col-sm-1 col-md-2 icon"><span data-icon="&#xe007;" /></div>
<div class="col-sm-1 col-md-2 icon"><span data-icon="&#xe007;"></span></div>
<div class="col-sm-11 col-md-10">
<p><strong>Use Status Indicator</strong><br/>
The Status indicator helps you manage Tasks and Issues by giving them one of 3 different colour: Grey, Green or Red. The meaning of these Statuses can be freely configured, for example:
@@ -414,7 +414,7 @@
</div>
</div>
<div class="row">
<div class="col-sm-1 col-md-2 icon"><span data-icon="&#xe017;" /></div>
<div class="col-sm-1 col-md-2 icon"><span data-icon="&#xe017;"></span></div>
<div class="col-sm-11 col-md-10">
<p><strong>Set Priorities</strong><br/>
The <i class="fa fa-star"/> is used to indicate priority: in Kanban views, high priority Tasks or Issues will be displayed on top. This is particulary useful if you use a Scrum methodology to indicate the tasks for the week. Similarly to Status, you can change the meaning of the Star indicator from the Project Stages tab.
@@ -422,7 +422,7 @@
</div>
</div>
<div class="row">
<div class="col-sm-1 col-md-2 icon"><span data-icon="&#xe001;" /></div>
<div class="col-sm-1 col-md-2 icon"><span data-icon="&#xe001;"></span></div>
<div class="col-sm-11 col-md-10">
<p><strong>Have Clear Responsibilities</strong><br/>
The person assigned to a task is responsible for its progress and their avatar is displayed in Kanban view for quick reference. Of course, the responsibility for a task can change depending on its stage.
@@ -470,7 +470,7 @@
<div class="panel panel-success">
<div class="panel-heading">
<span class="panel-title">
<span class="fa fa-tasks"/>
<span class="fa fa-tasks"></span>
<strong> Directly in Odoo</strong>
</span>
</div>
@@ -488,7 +488,7 @@
<div class="panel panel-success">
<div class="panel-heading">
<span class="panel-title">
<span class="fa fa-puzzle-piece"/>
<span class="fa fa-puzzle-piece"></span>
<strong> Via Chrome extension</strong>
</span>
</div>
@@ -509,7 +509,7 @@
<div class="panel panel-success">
<div class="panel-heading">
<span class="panel-title">
<span class="fa fa-mobile"/>
<span class="fa fa-mobile"></span>
<strong> From your Mobile phone</strong>
</span>
</div>
@@ -570,9 +570,9 @@
<div class="well well-lg">
<strong>Need help with Timesheets management? Contact us now:</strong><br/>
<a href="https://www.odoo.com/page/live-chat" target="_blank" style="margin-right:10px">
<span class="fa fa-comment-o"/> Website Live Chat on
<span class="fa fa-comment-o"></span> Website Live Chat on
</a>
<a href="https://www.odoo.com/help"><span class="fa fa-envelope-o"/> Email Our Project Expert</a>
<a href="https://www.odoo.com/help"><span class="fa fa-envelope-o"></span> Email Our Project Expert</a>
</div>
</section>
</t>
@@ -588,7 +588,7 @@
<div class="panel panel-success">
<div class="panel-heading">
<span class="panel-title">
<span class="fa fa-laptop"/>
<span class="fa fa-laptop"></span>
<strong> Screen Customization</strong>
</span>
</div>
@@ -607,7 +607,7 @@
<div class="panel panel-success">
<div class="panel-heading">
<span class="panel-title">
<span class="fa fa-sitemap"/>
<span class="fa fa-sitemap"></span>
<strong> Workflow Customization</strong>
</span>
</div>
@@ -624,7 +624,7 @@
<div class="panel panel-success">
<div class="panel-heading">
<span class="panel-title">
<span class="fa fa-pencil-square-o"/>
<span class="fa fa-pencil-square-o"></span>
<strong> Create Custom Reports</strong>
</span>
</div>
@@ -650,8 +650,8 @@
<div class="col-md-4 col-xs-12">
<div class="well well-lg">
<strong>Contact us now:</strong><br/>
<a href="https://www.odoo.com/page/live-chat" target="_blank" style="margin-right:10px"><span class="fa fa-comment-o"/> Website Live Chat on</a><br/>
<a href="https://www.odoo.com/help"><span class="fa fa-envelope-o"/> Email Our Project Expert</a>
<a href="https://www.odoo.com/page/live-chat" target="_blank" style="margin-right:10px"><span class="fa fa-comment-o"></span> Website Live Chat on</a><br/>
<a href="https://www.odoo.com/help"><span class="fa fa-envelope-o"></span> Email Our Project Expert</a>
</div>
</div>
</div>
@@ -671,7 +671,7 @@
<p>Create bills automatically based on Time &amp; Material.</p>
<a t-att-href="prepare_backend_url('base.open_module_tree', 'form', 'account')"
class="btn odoo_purple">
<span class="fa fa-arrow-circle-o-down"/> Install now
<span class="fa fa-arrow-circle-o-down"></span> Install now
</a>
</div>
<div class="text-center col-md-4 col-xs-6">
@@ -680,7 +680,7 @@
<p>Organize your company, from personal tasks to collaborative meeting minutes.</p>
<a t-att-href="prepare_backend_url('base.open_module_tree', 'form', 'note')"
class="btn odoo_purple">
<span class="fa fa-arrow-circle-o-down"/> Install now
<span class="fa fa-arrow-circle-o-down"></span> Install now
</a>
</div>
</div>
@@ -822,7 +822,7 @@
</div>
</div>
<div class="alert alert-info info_icon" role="alert">
<span class="fa fa-lightbulb-o fa-lg"/>
<span class="fa fa-lightbulb-o fa-lg"></span>
<h4><strong>There are two ways of managing your Kanban stages:</strong></h4>
<li><strong>In 'Push' mode</strong>, tasks are pushed into the next stage (once they satisfy all requirements) by the person responsible for the current stage. This is a simple way to work but only functions well if you work alone.</li>
<li><strong>In 'Pull' mode</strong>, tasks ready to progress to the next stage are just marked as 'Ready for next stage (using the status icon) by the person responsible for the current stage. Then, the person responsible for the next stage takes the task and moves it to the next stage. This is the best way to work if you have diluted responsibilities for your Kanban stages.</li>
@@ -834,7 +834,7 @@
<p class="text-muted">Managing notifications is essential: too few and you risk missing critical information, too many and you will be overloaded with unnecessary information. The trick is to find the right balance between the projects, stages and tasks you want to be informed about. Fortunately, Odoo Project has many levels of notifications and messages you can choose from.</p>
<div class="o_planner_paragraphs">
<div class="row">
<div class="col-md-2 icon"><span data-icon="&#xe051;" /></div>
<div class="col-md-2 icon"><span data-icon="&#xe051;"></span></div>
<div class="col-md-7">
<p>
<strong>Follow only what you need</strong><br/>
@@ -850,7 +850,7 @@
</div>
</div>
<div class="row">
<div class="col-md-2 icon"><span data-icon="&#xe04a;" /></div>
<div class="col-md-2 icon"><span data-icon="&#xe04a;"></span></div>
<div class="col-md-10">
<p>
<strong>Rely on the chatter</strong><br/>
@@ -861,7 +861,7 @@
</div>
</div>
<div class="row">
<div class="col-md-2 icon"><span data-icon="&#xe035;" /></div>
<div class="col-md-2 icon"><span data-icon="&#xe035;"></span></div>
<div class="col-md-10">
<p>
<strong>Be careful about the messages you send</strong><br/>
@@ -876,7 +876,7 @@
</div>
<div class="row">
<div class="col-md-2 icon"><span data-icon="&#xe01f;" /></div>
<div class="col-md-2 icon"><span data-icon="&#xe01f;"></span></div>
<div class="col-md-10">
<p>
<strong>Create tasks and issues by email</strong><br/>
@@ -890,7 +890,7 @@
</p>
</div>
<div class="alert alert-info info_icon" role="alert">
<span class="fa fa-lightbulb-o fa-lg"/>
<span class="fa fa-lightbulb-o fa-lg"></span>
<li>Internal notes are messages that will appear in the Chatter but will not be notified in Odoo's Inbox.</li>
<li>You can reply directly to a message from you email software; the message and its attachments will be added to the Chatter.</li>
<li>If you don't want to receive email notifications, you can uncheck the option in your <a t-att-href="prepare_backend_url('base.action_res_users', 'form')">Account Preferences</a>.</li>
@@ -911,7 +911,7 @@
</p>
<div class="o_planner_paragraphs">
<div class="row">
<div class="hidden-xs hidden-sm col-md-2 icon"><span data-icon="&#xe001;"/></div>
<div class="hidden-xs hidden-sm col-md-2 icon"><span data-icon="&#xe001;"></span></div>
<div class="col-xs-12 col-md-10">
<p>
<strong>Ask participants to prepare</strong><br/>
@@ -922,7 +922,7 @@
</div>
</div>
<div class="row">
<div class="hidden-xs hidden-sm col-md-2 icon"><span data-icon="&#xe003;"/></div>
<div class="hidden-xs hidden-sm col-md-2 icon"><span data-icon="&#xe003;"></span></div>
<div class="col-xs-12 col-md-10">
<p>
<strong>Prepare yourself as well</strong><br/>
@@ -933,7 +933,7 @@
</div>
</div>
<div class="row">
<div class="hidden-xs hidden-sm col-md-2 icon"><span data-icon="&#xe04c;"/></div>
<div class="hidden-xs hidden-sm col-md-2 icon"><span data-icon="&#xe04c;"></span></div>
<div class="col-xs-12 col-md-10">
<p>
<strong>Start with an answer.</strong><br/>
@@ -945,7 +945,7 @@
</div>
</div>
<div class="row">
<div class="hidden-xs hidden-sm col-md-2 icon"><span data-icon="&#xe04f;"/></div>
<div class="hidden-xs hidden-sm col-md-2 icon"><span data-icon="&#xe04f;"></span></div>
<div class="col-xs-12 col-md-10">
<strong> Prepare an agenda (and keep to it)</strong>
<p>Be aware of the team’s productivity and time: try to keep meetings to one hour or less.</p>
@@ -967,7 +967,7 @@
</div>
</div>
<div class="row">
<div class="hidden-xs hidden-sm col-md-2 icon"><span data-icon="&#xe01e;"/></div>
<div class="hidden-xs hidden-sm col-md-2 icon"><span data-icon="&#xe01e;"></span></div>
<div class="col-xs-12 col-md-10">
<p>
<strong>Follow every meeting with a recap email</strong>
@@ -976,7 +976,7 @@
</div>
</div>
<div class="row">
<div class="hidden-xs hidden-sm col-md-2 icon"><span data-icon="&#xe075;"/></div>
<div class="hidden-xs hidden-sm col-md-2 icon"><span data-icon="&#xe075;"></span></div>
<div class="col-xs-12 col-md-10">
<p>
<strong>Choose the right day</strong><br/>
@@ -996,7 +996,7 @@
<div class="panel panel-success">
<div class="panel-heading">
<span class="panel-title">
<span class="fa fa-thumb-tack"/>
<span class="fa fa-thumb-tack"></span>
<strong> Exercise 1</strong><br/>
<span class="small">Check Workload</span>
</span>
@@ -1015,7 +1015,7 @@
<div class="panel panel-success">
<div class="panel-heading">
<span class="panel-title">
<span class="fa fa-thumb-tack"/>
<span class="fa fa-thumb-tack"></span>
<strong> Exercise 2</strong><br/>
<span class="small">Delay to close an Issue</span>
</span>
@@ -1032,7 +1032,7 @@
</div>
</div>
<div class="alert alert-info info_icon" role="alert">
<span class="fa fa-lightbulb-o fa-lg"/>
<span class="fa fa-lightbulb-o fa-lg"></span>
<li>You can save your reports to easily reuse it later</li>
<li>You can even include any report in your dashboard for permanent access!</li>
</div>
@@ -1046,8 +1046,8 @@
<div class="col-md-4 col-xs-12">
<div class="well well-lg">
<strong>Contact us now:</strong><br/>
<a href="https://www.odoo.com/page/live-chat" target="_blank" style="margin-right:10px"><span class="fa fa-comment-o"/> Website Live Chat on</a><br/>
<a href="https://www.odoo.com/help"><span class="fa fa-envelope-o"/> Email Our Project Expert</a>
<a href="https://www.odoo.com/page/live-chat" target="_blank" style="margin-right:10px"><span class="fa fa-comment-o"></span> Website Live Chat on</a><br/>
<a href="https://www.odoo.com/help"><span class="fa fa-envelope-o"></span> Email Our Project Expert</a>
</div>
</div>
</div>
+1
View File
@@ -450,6 +450,7 @@ class PurchaseOrder(models.Model):
result = action.read()[0]
#override the context to get rid of the default filtering on picking type
result.pop('id', None)
result['context'] = {}
pick_ids = sum([order.picking_ids.ids for order in self], [])
#choose the view_mode accordingly
+1 -1
View File
@@ -51,7 +51,7 @@ class ReportController(Controller):
#------------------------------------------------------
# Misc. route utils
#------------------------------------------------------
@route(['/report/barcode', '/report/barcode/<type>/<path:value>'], type='http', auth="user")
@route(['/report/barcode', '/report/barcode/<type>/<path:value>'], type='http', auth="public")
def report_barcode(self, type, value, width=600, height=100, humanreadable=0):
"""Contoller able to render barcode images thanks to reportlab.
Samples:
@@ -17,7 +17,7 @@ class ir_actions_report(models.Model):
"""
self.ensure_one()
action_ref = self.env.ref('base.action_ui_view')
if not action_ref:
if not action_ref or len(self.report_name.split('.')) < 2:
return False
action_data = action_ref.read()[0]
action_data['domain'] = [('name', 'ilike', self.report_name.split('.')[1]), ('type', '=', 'qweb')]
@@ -36,7 +36,7 @@
<field name="external_report_layout" class="oe_inline"/>
</div>
<div class="mt16">
<button name="change_report_template" string="Change Report Template" type="object" class="oe_link"/>
<button name="change_report_template" string="Change Report Template" type="object" class="oe_link" icon="fa-arrow-right"/>
<button name="edit_external_header" string="Edit Header" type="object" class="oe_link" groups="base.group_no_one"/>
<button name="%(action_report_externalpreview)d" string="Preview Report" type="action" class="oe_link" groups="base.group_no_one"/>
</div>
@@ -29,9 +29,11 @@
<field name="model">product.template</field>
<field name="inherit_id" ref="stock.view_template_property_form"/>
<field name="arch" type="xml">
<field name="categ_id" position="after">
<field name="intrastat_id"/>
</field>
<group name="group_lots_and_weight" position="after">
<group>
<field name="intrastat_id"/>
</group>
</group>
</field>
</record>
+4 -2
View File
@@ -506,10 +506,12 @@ class SaleOrder(models.Model):
for tax in line.tax_id:
group = tax.tax_group_id
res.setdefault(group, 0.0)
amount = tax.compute_all(line.price_reduce + base_tax, quantity=line.product_uom_qty)['taxes'][0]['amount']
amount = tax.compute_all(line.price_reduce + base_tax, quantity=line.product_uom_qty,
product=line.product_id, partner=line.order_partner_id)['taxes'][0]['amount']
res[group] += amount
if tax.include_base_amount:
base_tax += tax.compute_all(line.price_reduce + base_tax, quantity=1)['taxes'][0]['amount']
base_tax += tax.compute_all(line.price_reduce + base_tax, quantity=1, product=line.product_id,
partner=line.order_partner_id)['taxes'][0]['amount']
res = sorted(res.items(), key=lambda l: l[0].sequence)
res = map(lambda l: (l[0].name, l[1]), res)
return res
+2 -2
View File
@@ -87,7 +87,7 @@ class SaleConfiguration(models.TransientModel):
@api.model
def get_default_sale_pricelist_setting(self, fields):
sale_pricelist_setting = self.env['ir.values'].get_default('sales.config.settings', 'sale_pricelist_setting')
sale_pricelist_setting = self.env['ir.values'].get_default('sale.config.settings', 'sale_pricelist_setting')
multi_sales_price = sale_pricelist_setting in ['percentage', 'formula']
return {
'sale_pricelist_setting': sale_pricelist_setting,
@@ -118,7 +118,7 @@ class SaleConfiguration(models.TransientModel):
})
def set_default_sale_pricelist_setting(self):
return self.env['ir.values'].sudo().set_default('sales.config.settings', 'sale_pricelist_setting', self.sale_pricelist_setting)
return self.env['ir.values'].sudo().set_default('sale.config.settings', 'sale_pricelist_setting', self.sale_pricelist_setting)
def set_deposit_product_id_defaults(self):
return self.env['ir.values'].sudo().set_default('sale.config.settings', 'deposit_product_id_setting', self.deposit_product_id_setting.id)
@@ -72,9 +72,6 @@
<div class="mt16">
<button name="%(base.action_currency_form)d" icon="fa-arrow-right" type="action" string="Currencies" class="btn-link"/>
</div>
<div>
<button name="%(account.action_account_config)d" icon="fa-arrow-right" type="action" string="Apply Automatic Rates Update" class="btn-link"/>
</div>
</div>
</div>
</div>
@@ -188,7 +185,7 @@
</div>
<div class="content-group" attrs="{'invisible': [('default_use_sale_note','=',False)]}">
<div class="mt16">
<field name="sale_note"/>
<field name="sale_note" placeholder="Insert your terms &amp; conditions here..."/>
</div>
</div>
</div>
@@ -4,6 +4,9 @@
@sale-table-spacing: 10px;
.o_kanban_record {
span {
display: inline-block;
}
min-height: 250px;
@media (min-width: @screen-sm-min) {
min-width: 450px;
@@ -84,7 +84,7 @@
</a>
</td>
<td t-if="show_demo or (values and values['closing']['overdue'] != 0)" class="o_warning" title="To Opportunities">
<a href="#" class="o_dashboard_action" name="crm.action_your_pipeline" data-context="{'search_default_activities_overdue':'1'}">
<a href="#" class="o_dashboard_action" name="crm.action_your_pipeline" data-context="{'search_default_overdue_opp':'1'}">
<t t-if="!show_demo">
<t t-esc="values['closing']['overdue']"/> exp. closing
</t>
+2 -5
View File
@@ -13,7 +13,7 @@ OPERATORS = {
'>': py_operator.gt,
'<=': py_operator.le,
'>=': py_operator.ge,
'==': py_operator.eq,
'=': py_operator.eq,
'!=': py_operator.ne
}
@@ -260,9 +260,6 @@ class Product(models.Model):
if not isinstance(value, (float, int)):
raise UserError('Invalid domain right operand')
if operator == '=':
operator = '=='
# TODO: Still optimization possible when searching virtual quantities
ids = []
for product in self.search([]):
@@ -272,7 +269,7 @@ class Product(models.Model):
def _search_qty_available(self, operator, value):
# TDE FIXME: should probably clean the search methods
if value == 0.0 and operator in ('==', '>=', '<='):
if value == 0.0 and operator in ('=', '>=', '<='):
return self._search_product_quantity(operator, value, 'qty_available')
product_ids = self._search_qty_available_new(operator, value, self._context.get('lot_id'), self._context.get('owner_id'), self._context.get('package_id'))
return [('id', 'in', product_ids)]
+9 -5
View File
@@ -575,7 +575,9 @@ class StockMove(models.Model):
# in case the move is returned, we want to try to find quants before forcing the assignment
if not move.origin_returned_move_id:
continue
if move.product_id.type == 'consu':
# if the move is preceeded, restrict the choice of quants in the ones moved previously in original move
ancestors = move.find_move_ancestors()
if move.product_id.type == 'consu' and not ancestors:
moves_to_assign |= move
continue
else:
@@ -584,8 +586,6 @@ class StockMove(models.Model):
# we always search for yet unassigned quants
main_domain[move.id] = [('reservation_id', '=', False), ('qty', '>', 0)]
# if the move is preceeded, restrict the choice of quants in the ones moved previously in original move
ancestors = move.find_move_ancestors()
if move.state == 'waiting' and not ancestors:
# if the waiting move hasn't yet any ancestor (PO/MO not confirmed yet), don't find any quant available in stock
main_domain[move.id] += [('id', '=', False)]
@@ -797,7 +797,11 @@ class StockMove(models.Model):
# compute quantities for each lot + check quantities match
lot_quantities = dict((pack_lot.lot_id.id, operation.product_uom_id._compute_quantity(pack_lot.qty, operation.product_id.uom_id)
) for pack_lot in operation.pack_lot_ids)
if operation.pack_lot_ids and float_compare(sum(lot_quantities.values()), operation.product_qty, precision_rounding=operation.product_uom_id.rounding) != 0.0:
qty = operation.product_qty
if operation.product_uom_id and operation.product_uom_id != operation.product_id.uom_id:
qty = operation.product_uom_id._compute_quantity(qty, operation.product_id.uom_id)
if operation.pack_lot_ids and float_compare(sum(lot_quantities.values()), qty, precision_rounding=operation.product_id.uom_id.rounding) != 0.0:
raise UserError(_('You have a difference between the quantity on the operation and the quantities specified for the lots. '))
quants_taken = []
@@ -972,4 +976,4 @@ class StockMove(models.Model):
def quants_unreserve(self):
self.filtered(lambda x: x.partially_available).write({'partially_available': False})
self.mapped('reserved_quant_ids').sudo().write({'reservation_id': False})
self.mapped('reserved_quant_ids').sudo().write({'reservation_id': False})
+3 -5
View File
@@ -34,8 +34,7 @@ class PackOperation(models.Model):
product_qty = fields.Float('To Do', default=0.0, digits=dp.get_precision('Product Unit of Measure'), required=True)
ordered_qty = fields.Float('Ordered Quantity', digits=dp.get_precision('Product Unit of Measure'))
qty_done = fields.Float('Done', default=0.0, digits=dp.get_precision('Product Unit of Measure'))
# TDE FIXME: what what what what ??
is_done = fields.Boolean(compute='_compute_is_done', inverse='_set_is_done', string='Done', oldname='processed_boolean')
is_done = fields.Boolean(compute='_compute_is_done', string='Done', readonly=False, oldname='processed_boolean')
package_id = fields.Many2one('stock.quant.package', 'Source Package')
pack_lot_ids = fields.One2many('stock.pack.operation.lot', 'operation_id', 'Lots/Serial Numbers Used')
result_package_id = fields.Many2one(
@@ -73,9 +72,8 @@ class PackOperation(models.Model):
def _compute_is_done(self):
self.is_done = self.qty_done > 0.0
@api.one
def _set_is_done(self):
# TDE FIXME: whuuuut ???
@api.onchange('is_done')
def on_change_is_done(self):
if not self.product_id:
if self.is_done and self.qty_done == 0:
self.qty_done = 1.0
+13 -14
View File
@@ -316,22 +316,18 @@ class Picking(models.Model):
self.state = 'cancel'
elif all(move.state in ['cancel', 'done'] for move in self.move_lines):
self.state = 'done'
elif self.move_type == 'one':
ordered_moves = self.move_lines.filtered(
lambda move: move.state not in ['cancel', 'done']
).sorted(
key=lambda move: (move.state == 'assigned' and 2) or (move.state == 'waiting' and 1) or 0, reverse=False
)
self.state = ordered_moves[0].state
else:
filtered_moves = self.move_lines.filtered(lambda move: move.state not in ['cancel', 'done'])
if not all(move.state == 'assigned' for move in filtered_moves) and any(move.state == 'assigned' for move in filtered_moves):
self.state = 'partially_available'
elif any(move.partially_available for move in filtered_moves):
# We sort our moves by importance of state: "confirmed" should be first, then we'll have
# "waiting" and finally "assigned" at the end.
moves_todo = self.move_lines\
.filtered(lambda move: move.state not in ['cancel', 'done'])\
.sorted(key=lambda move: (move.state == 'assigned' and 2) or (move.state == 'waiting' and 1) or 0)
if self.move_type == 'one':
self.state = moves_todo[0].state
elif moves_todo[0].state != 'assigned' and any(x.partially_available or x.state == 'assigned' for x in moves_todo):
self.state = 'partially_available'
else:
ordered_moves = filtered_moves.sorted(key=lambda move: (move.state == 'assigned' and 2) or (move.state == 'waiting' and 1) or 0, reverse=True)
self.state = ordered_moves[0].state
self.state = moves_todo[-1].state
@api.one
@api.depends('move_lines.priority')
@@ -597,7 +593,10 @@ class Picking(models.Model):
continue
move_quants = move.reserved_quant_ids
picking_quants += move_quants
forced_qty = (move.state == 'assigned') and move.product_qty - sum([x.qty for x in move_quants]) or 0
forced_qty = 0.0
if move.state == 'assigned':
qty = move.product_uom._compute_quantity(move.product_uom_qty, move.product_id.uom_id, round=False)
forced_qty = qty - sum([x.qty for x in move_quants])
# if we used force_assign() on the move, or if the move is incoming, forced_qty > 0
if float_compare(forced_qty, 0, precision_rounding=move.product_id.uom_id.rounding) > 0:
if forced_qties.get(move.product_id):
+195 -4
View File
@@ -1210,7 +1210,6 @@ class TestStockFlow(TestStockCommon):
inventory5.action_done()
self.assertEqual(inventory5_lines_count, inventory4_lines_count + 1, "The new product is not taken into account in the inventory valuation.")
self.assertTrue(product.id in inventory5.line_ids.mapped('product_id').ids, "The new product is not take into account in the inventory valuation.")
def test_30_check_with_no_incoming_lot(self):
""" Picking in without lots and picking out with"""
@@ -1278,7 +1277,6 @@ class TestStockFlow(TestStockCommon):
self.assertEqual(sum([x.qty for x in quants if x.lot_id.id == lot2.id]), 1.0, 'Wrong sum of quants with lot 2')
self.assertEqual(sum([x.qty for x in quants if x.lot_id.id == lot3.id]), 2.0, 'Wrong sum of quants with lot 3')
def test_40_pack_in_pack(self):
""" Put a pack in pack"""
picking_out = self.PickingObj.create({
@@ -1368,7 +1366,8 @@ class TestStockFlow(TestStockCommon):
# set the pack in pack operation to 'done'
for pack in picking_pack.pack_operation_pack_ids:
pack.qty_done = 1.0
pack.is_done = True
pack.on_change_is_done()
# Put in a pack
picking_pack.put_in_pack()
@@ -1386,7 +1385,8 @@ class TestStockFlow(TestStockCommon):
# set the pack in pack operation to 'done'
for pack in picking_out.pack_operation_pack_ids:
pack.qty_done = 1.0
pack.is_done = True
pack.on_change_is_done()
# Validate picking
picking_out.do_new_transfer()
@@ -1536,3 +1536,194 @@ class TestStockFlow(TestStockCommon):
# We should also make sure that when matching stock moves with pack operations, it takes the correct
self.assertEqual(len(picking_out.move_lines[0].linked_move_operation_ids), 2, 'We should only have 2 links beween the move and the 2 operations')
self.assertEqual(len(picking_out.move_lines[0].quant_ids), 2, 'We should have exactly 2 quants in the end')
def test_70_picking_state_all_at_once_reserve(self):
""" This test will check that the state of the picking is correctly computed according
to the state of its move lines and its move type.
"""
# move_type: direct == partial, one == all at once
# picking: confirmed == waiting availability, partially_available = partially available
# -----------------------------------------------------------
# "all at once" and "reserve" scenario
# -----------------------------------------------------------
# get one product in stock
inventory = self.env['stock.inventory'].create({
'name': 'Inventory Product Table',
'filter': 'partial',
'line_ids': [(0, 0, {
'product_id': self.productA.id,
'product_uom_id': self.productA.uom_id.id,
'product_qty': 1,
'location_id': self.stock_location
})]
})
inventory.action_done()
# create a "all at once" delivery order for two products
picking_out = self.PickingObj.create({
'partner_id': self.partner_agrolite_id,
'picking_type_id': self.picking_type_out,
'location_id': self.stock_location,
'location_dest_id': self.customer_location})
picking_out.move_type = 'one'
self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 2,
'product_uom': self.productA.uom_id.id,
'picking_id': picking_out.id,
'location_id': self.stock_location,
'location_dest_id': self.customer_location})
# validate this delivery order, it should be in the waiting state
picking_out.action_assign()
self.assertEquals(picking_out.state, "confirmed")
# receive one product in stock
inventory = self.env['stock.inventory'].create({
'name': 'Inventory Product Table',
'filter': 'partial',
'line_ids': [(0, 0, {
'product_id': self.productA.id,
'product_uom_id': self.productA.uom_id.id,
'product_qty': 2,
'location_id': self.stock_location
})]
})
inventory.action_done()
# recheck availability of the delivery order, it should be assigned
picking_out.action_assign()
self.assertEquals(picking_out.state, "assigned")
def test_71_picking_state_all_at_once_force_assign(self):
""" This test will check that the state of the picking is correctly computed according
to the state of its move lines and its move type.
"""
# move_type: direct == partial, one == all at once
# picking: confirmed == waiting availability, partially_available = partially available
# -----------------------------------------------------------
# "all at once" and "force assign" scenario
# -----------------------------------------------------------
# create a "all at once" delivery order for two products
picking_out = self.PickingObj.create({
'partner_id': self.partner_agrolite_id,
'picking_type_id': self.picking_type_out,
'location_id': self.stock_location,
'location_dest_id': self.customer_location})
picking_out.move_type = 'direct'
self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 2,
'product_uom': self.productA.uom_id.id,
'picking_id': picking_out.id,
'location_id': self.stock_location,
'location_dest_id': self.customer_location})
# validate this delivery order, it should be in the waiting state
picking_out.action_assign()
self.assertEquals(picking_out.state, "confirmed")
# force assign on the delivery order, it should be assigned
picking_out.force_assign()
self.assertEquals(picking_out.state, "assigned")
def test_72_picking_state_partial_reserve(self):
""" This test will check that the state of the picking is correctly computed according
to the state of its move lines and its move type.
"""
# move_type: direct == partial, one == all at once
# picking: confirmed == waiting availability, partially_available = partially available
# -----------------------------------------------------------
# "partial" and "reserve" scenario
# -----------------------------------------------------------
# get one product in stock
inventory = self.env['stock.inventory'].create({
'name': 'Inventory Product Table',
'filter': 'partial',
'line_ids': [(0, 0, {
'product_id': self.productA.id,
'product_uom_id': self.productA.uom_id.id,
'product_qty': 1,
'location_id': self.stock_location
})]
})
inventory.action_done()
# create a "partial" delivery order for two products
picking_out = self.PickingObj.create({
'partner_id': self.partner_agrolite_id,
'picking_type_id': self.picking_type_out,
'location_id': self.stock_location,
'location_dest_id': self.customer_location})
picking_out.move_type = 'direct'
self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 2,
'product_uom': self.productA.uom_id.id,
'picking_id': picking_out.id,
'location_id': self.stock_location,
'location_dest_id': self.customer_location})
# validate this delivery order, it should be in partially available
picking_out.action_assign()
self.assertEquals(picking_out.state, "partially_available")
# receive one product in stock
inventory = self.env['stock.inventory'].create({
'name': 'Inventory Product Table',
'filter': 'partial',
'line_ids': [(0, 0, {
'product_id': self.productA.id,
'product_uom_id': self.productA.uom_id.id,
'product_qty': 2,
'location_id': self.stock_location
})]
})
inventory.action_done()
# recheck availability of the delivery order, it should be assigned
picking_out.action_assign()
self.assertEquals(picking_out.state, "assigned")
def test_73_picking_state_partial_force_assign(self):
""" This test will check that the state of the picking is correctly computed according
to the state of its move lines and its move type.
"""
# move_type: direct == partial, one == all at once
# picking: confirmed == waiting availability, partially_available = partially available
# -----------------------------------------------------------
# "partial" and "force assign" scenario
# -----------------------------------------------------------
picking_out = self.PickingObj.create({
'partner_id': self.partner_agrolite_id,
'picking_type_id': self.picking_type_out,
'location_id': self.stock_location,
'location_dest_id': self.customer_location})
picking_out.move_type = 'direct'
self.MoveObj.create({
'name': self.productA.name,
'product_id': self.productA.id,
'product_uom_qty': 2,
'product_uom': self.productA.uom_id.id,
'picking_id': picking_out.id,
'location_id': self.stock_location,
'location_dest_id': self.customer_location})
# validate this delivery order, it should be in the waiting state
picking_out.action_assign()
self.assertEquals(picking_out.state, "confirmed")
# force assign on the delivery order, it should be assigned
picking_out.force_assign()
self.assertEquals(picking_out.state, "assigned")
-3
View File
@@ -140,9 +140,6 @@
<field name="volume" string="Volume"/>
</div>
</group>
<group>
<field name="categ_id" string="Internal Category"/>
</group>
<group name="stock_property" groups="base.group_no_one">
<field name="property_stock_procurement" domain="[('usage','=','procurement')]"/>
<field name="property_stock_production" domain="[('usage','=','production')]"/>
@@ -15,7 +15,6 @@
<div class="oe_button_box" name="button_box">
<button name="%(lot_open_quants)d" icon="fa-arrows" class="oe_stat_button" string="Locate" type="action" attrs="{'invisible': [('quant_ids','=',[])]}"/>
<button name="action_traceability" icon="fa-arrow-up" class="oe_stat_button" string="Traceability" type="object" attrs="{'invisible': [('quant_ids','=',[])]}"/>
<field name="quant_ids" invisible="True"/>
</div>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
@@ -36,7 +36,7 @@ class ProductChangeQuantity(models.TransientModel):
def onchange_location_id(self):
# TDE FIXME: should'nt we use context / location ?
if self.location_id and self.product_id:
availability = self.product_id._product_available()
availability = self.product_id.with_context(compute_child=False)._product_available()
self.new_quantity = availability[self.product_id.id]['qty_available']
@api.onchange('product_id')
+1 -1
View File
@@ -79,7 +79,7 @@ class StockHistory(models.Model):
product_template_id,
SUM(quantity) as quantity,
date,
SUM(price_unit_on_quant * quantity) / SUM(quantity) as price_unit_on_quant,
COALESCE(SUM(price_unit_on_quant * quantity) / NULLIF(SUM(quantity), 0), 0) as price_unit_on_quant,
source,
string_agg(DISTINCT serial_number, ', ' ORDER BY serial_number) AS serial_number
FROM
+1 -1
View File
@@ -638,7 +638,7 @@ class SurveyQuestion(models.Model):
if self.comments_allowed:
comment_answer = answer_candidates.pop(("%s_%s" % (answer_tag, 'comment')), '').strip()
# Preventing answers with blank value
if all([True if not answer.strip() else False for answer in answer_candidates.values()]):
if all([True if not answer.strip() else False for answer in answer_candidates.values()]) and answer_candidates:
errors.update({answer_tag: self.constr_error_msg})
# There is no answer neither comments (if comments count as answer)
if not answer_candidates and self.comment_count_as_answer and (not comment_flag or not comment_answer):
+16
View File
@@ -587,6 +587,22 @@ class WebClient(http.Controller):
class Proxy(http.Controller):
@http.route('/web/proxy/load', type='json', auth="none")
def load(self, path):
""" Proxies an HTTP request through a JSON request.
It is strongly recommended to not request binary files through this,
as the result will be a binary data blob as well.
:param path: actual request path
:return: file content
"""
from werkzeug.test import Client
from werkzeug.wrappers import BaseResponse
base_url = request.httprequest.base_url
return Client(request.httprequest.app, BaseResponse).get(path, base_url=base_url).data
@http.route('/web/proxy/post/<path:path>', type='http', auth='user', methods=['GET'])
def post(self, path):
"""Effectively execute a POST request that was hooked through user login"""
@@ -86,8 +86,11 @@ var Session = core.Class.extend(mixins.EventDispatcherMixin, {
* Init a session, reloads from cookie, if it exists
*/
session_init: function () {
var def = this.session_reload();
if (this.is_frontend) return def;
var self = this;
return this.session_reload().then(function() {
return def.then(function() {
var modules = self.module_list.join(',');
var deferred = self.load_qweb(modules);
if(self.session_is_valid()) {
@@ -26,7 +26,7 @@ var M2ODialog = Dialog.extend({
title: _.str.sprintf(_t("Create a %s"), parent.string),
size: 'medium',
buttons: [
{text: _t('Create'), classes: 'btn-primary', click: function() {
{text: _t('Create'), classes: 'btn-primary', click: function(e) {
if (this.$("input").val() !== ''){
this.getParent()._quick_create(this.$("input").val());
this.close();
@@ -997,7 +997,9 @@ var X2ManyListView = ListView.extend({
*/
var X2ManyList = ListView.List.extend({
pad_table_to: function (count) {
if (!this.view.is_action_enabled('create') || this.view.x2m.get('effective_readonly')) {
var ftype = this.view.x2m.field.type;
var is_readonly = this.view.x2m.get('effective_readonly');
if (is_readonly || (ftype === 'one2many' && !this.view.is_action_enabled('create'))) {
this._super(count);
return;
}
@@ -1304,7 +1306,7 @@ var Many2ManyListView = X2ManyListView.extend({
context: this.x2m.build_context(),
title: _t("Add: ") + this.x2m.string,
alternative_form_view: this.x2m.field.views ? this.x2m.field.views.form : undefined,
no_create: this.x2m.options.no_create,
no_create: this.x2m.options.no_create || !this.is_action_enabled('create'),
on_selected: function(element_ids) {
return self.x2m.data_link_multi(element_ids).then(function() {
self.x2m.reload_current_view();
@@ -697,6 +697,12 @@ var FieldBooleanButton = common.AbstractField.extend({
this.string_false = _t("Archived");
this.hover_false = _t("Restore");
break;
case "close":
this.string_true = _t("Active");
this.string_false = _t("Closed");
this.hover_true = _t("Close");
this.hover_false = _t("Open");
break;
default:
var terms = typeof this.options["terminology"] === 'string' ? {} : this.options["terminology"];
this.string_true = _t(terms.string_true || "On");
+1 -1
View File
@@ -1508,7 +1508,7 @@ ListView.Groups = Class.extend({
// drag and drop enabled if list is not sorted (unless it is sorted by
// its sequence field (ASC)), and there is a visible column with
// @widget=handle or "sequence" column in the view.
if ((dataset.sort && [seqname, seqname + 'ASC', ''].indexOf(dataset.sort()) === -1)
if ((dataset.sort && [seqname, seqname + ' ASC', ''].indexOf(dataset.sort()) === -1)
|| !_(this.columns).findWhere({'name': seqname})) {
return;
}
+6 -1
View File
@@ -57,6 +57,7 @@ var PivotView = View.extend({
this.headers = {};
this.cells = {};
this.has_data = false;
this.$buttons = $();
this.last_header_selected = null;
this.sorted_column = {};
@@ -199,7 +200,7 @@ var PivotView = View.extend({
this.initial_row_groupby = context.pivot_row_groupby || this.initial_row_groupby;
this.initial_col_groupby = context.pivot_col_groupby || this.initial_col_groupby;
}
this.main_row.groupbys = group_by.length ? group_by : this.initial_row_groupby.slice(0);
this.main_row.groupbys = group_by.length ? group_by : (context.pivot_row_groupby || this.initial_row_groupby.slice(0));
this.main_col.groupbys = context.pivot_column_groupby || this.initial_col_groupby.slice(0);
this.active_measures = context.pivot_measures || this.active_measures;
@@ -219,6 +220,10 @@ var PivotView = View.extend({
this.do_push_state({});
return this.data_loaded.done(function () {
self.display_table();
self.$buttons.find('.o_pivot_measures_list li').removeClass('selected');
self.active_measures.forEach(function (measure) {
self.$buttons.find('li[data-field="' + measure + '"]').addClass('selected');
});
_super();
});
},
@@ -12,32 +12,14 @@ var ModelFieldSelector = Widget.extend({
template: "FieldSelector",
events: {
// Handle popover opening and closing
"focusin input, .o_field_selector_popover": function (e) {
if (this._hidePopoverTimeout !== undefined) {
clearTimeout(this._hidePopoverTimeout);
this._hidePopoverTimeout = undefined;
return;
}
if (this.$input.is(e.currentTarget)) {
this.showPopover();
}
"focusin": function () {
clearTimeout(this._hidePopoverTimeout);
this.showPopover();
},
"focusout input, .o_field_selector_popover": function () {
if (this._hidePopoverTimeout !== undefined) {
return;
}
this._hidePopoverTimeout = setTimeout((function () {
this.hidePopover();
this._hidePopoverTimeout = undefined;
}).bind(this), 100);
},
"click .o_field_selector_close": function () {
if (this._hidePopoverTimeout !== undefined) {
clearTimeout(this._hidePopoverTimeout);
this._hidePopoverTimeout = undefined;
}
this.hidePopover();
"focusout": function () {
this._hidePopoverTimeout = _.defer(this.hidePopover.bind(this));
},
"click .o_field_selector_close": "hidePopover",
// Handle popover field navigation
"click .o_field_selector_prev_page": "goToPrevPage",
@@ -190,6 +172,7 @@ var ModelFieldSelector = Widget.extend({
},
/// The removeChainNode method removes the last field name at the end of the current field chain.
removeChainNode: function () {
this.dirty = true;
this.setChain(this.chain.substring(0, this.chain.lastIndexOf(".")));
},
/// The validate method toggles the valid status of the widget and display the error message if it
@@ -200,16 +183,20 @@ var ModelFieldSelector = Widget.extend({
this.valid = valid;
},
/// The showPopover method shows the popover to select the field chain. It prepares the popover pages
/// before actually showing it.
/// before actually showing it. (if already open, does nothing)
showPopover: function () {
if (this._isOpen) return;
this._isOpen = true;
this._prefill().then((function () {
this.displayPage();
this.$popover.removeClass("hidden");
}).bind(this));
},
/// The hidePopover method closes the popover and mark the field as selected. If the field chain changed,
/// it notifies its parents.
/// it notifies its parents. (if not open, does nothing)
hidePopover: function () {
if (!this._isOpen) return;
this._isOpen = false;
this.$popover.addClass("hidden");
this.isSelected = true;
if (this.dirty) {
+5 -1
View File
@@ -138,6 +138,10 @@
&.o_readonly {
visibility: visible;
color: transparent;
> .o_checkbox {
visibility: hidden;
}
}
}
}
@@ -158,7 +162,7 @@
max-width: 100%; // Fix a chrome bug where badge would not break on multiple lines
}
&.o_readonly {
&.o_readonly:not(.o_checkbox) {
background-color: transparent;
padding: 5px;
}
@@ -6,10 +6,10 @@
.o-position-absolute(0, 0, 1px);
.o-flex-display();
.o-align-items(center);
cursor: pointer;
&::after {
.o-caret-down();
cursor: pointer;
}
}
.o_field_selector_popover {
+5 -4
View File
@@ -49,15 +49,15 @@
<table class="o_group o_inner_group o_label_nowrap">
<tr>
<td class="o_td_label"><label for="old_pwd" class="o_form_label">Old Password</label></td>
<td width="100%"><input type="password" class="o_form_field o_form_input" name="old_pwd" minlength="1" autofocus="autofocus"/></td>
<td width="100%"><input type="password" class="o_form_field o_form_input" name="old_pwd" minlength="1" autofocus="autofocus" autocomplete="current-password"/></td>
</tr>
<tr>
<td class="o_td_label"><label for="new_password" class="o_form_label">New Password</label></td>
<td width="100%"><input type="password" class="o_form_field o_form_input" name="new_password" minlength="1"/></td>
<td width="100%"><input type="password" class="o_form_field o_form_input" name="new_password" minlength="1" autocomplete="new-password"/></td>
</tr>
<tr>
<td class="o_td_label"><label for="confirm_pwd" class="o_form_label">Confirm New Password</label></td>
<td width="100%"><input type="password" class="o_form_field o_form_input" name="confirm_pwd" minlength="1"/></td>
<td width="100%"><input type="password" class="o_form_field o_form_input" name="confirm_pwd" minlength="1" autocomplete="new-password"/></td>
</tr>
</table>
@@ -628,6 +628,7 @@
t-att-tabindex="widget.node.attrs.tabindex"
t-att-autofocus="widget.node.attrs.autofocus"
t-att-placeholder="widget.node.attrs.placeholder"
t-att-autocomplete="widget.password ? 'new-password' : widget.node.attrs.autocomplete"
t-att-maxlength="widget.field.size"/>
</t>
<t t-name="FormSelection">
@@ -803,7 +804,7 @@
<div t-name="FieldSelector" class="o_field_selector">
<input type="text" t-att-value="widget.chain" t-att-readonly="!(widget.options.debugMode) and 1 or None"/>
<div class="o_field_selector_controls">
<div class="o_field_selector_controls" tabindex="0">
<i class="fa fa-exclamation-triangle o_field_selector_warning hidden" title="Invalid field chain"/>
</div>
<div class="o_field_selector_popover hidden" tabindex="0">
+1 -1
View File
@@ -268,7 +268,7 @@
<div class="form-group field-password">
<label for="password" class="control-label">Password</label>
<input type="password" name="password" id="password" class="form-control" required="required" t-att-autofocus="'autofocus' if login else None" maxlength="4096"/>
<input type="password" name="password" id="password" class="form-control" required="required" autocomplete="current-password" t-att-autofocus="'autofocus' if login else None" maxlength="4096"/>
</div>
<p class="alert alert-danger" t-if="error">
@@ -177,6 +177,10 @@ var Translate = Widget.extend({
},
rte_changed: function (node) {
var $node = $(node);
$node.find("p").each(function () { // remove <p/> element which might have been inserted because of copy-paste
var $p = $(this);
$p.after($p.html()).remove();
});
var trans = this.getTranlationObject($node[0]);
$node.toggleClass('o_dirty', trans.value !== $node.html().replace(/[ \t\n\r]+/, ' '));
},

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