[MERGE] forward port branch saas-14 up to be9dfb331f
This commit is contained in:
@@ -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
|
||||
-
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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')"
|
||||
/>
|
||||
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -583,6 +583,9 @@
|
||||
<filter string="Overdue Activities" name="activities_overdue"
|
||||
domain="[('activity_ids.date_deadline', '<', 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', '<', 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)]"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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",
|
||||
|
||||
|
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()});
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.")
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 ---- */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(){
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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="" /></div>
|
||||
<div class="col-sm-1 col-md-2 icon"><span data-icon=""></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="" /></div>
|
||||
<div class="col-sm-1 col-md-2 icon"><span data-icon=""></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="" /></div>
|
||||
<div class="col-sm-1 col-md-2 icon"><span data-icon=""></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="" /></div>
|
||||
<div class="col-sm-1 col-md-2 icon"><span data-icon=""></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="" /></div>
|
||||
<div class="col-sm-1 col-md-2 icon"><span data-icon=""></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="" /></div>
|
||||
<div class="col-sm-1 col-md-2 icon"><span data-icon=""></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 & 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="" /></div>
|
||||
<div class="col-md-2 icon"><span data-icon=""></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="" /></div>
|
||||
<div class="col-md-2 icon"><span data-icon=""></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="" /></div>
|
||||
<div class="col-md-2 icon"><span data-icon=""></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="" /></div>
|
||||
<div class="col-md-2 icon"><span data-icon=""></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=""/></div>
|
||||
<div class="hidden-xs hidden-sm col-md-2 icon"><span data-icon=""></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=""/></div>
|
||||
<div class="hidden-xs hidden-sm col-md-2 icon"><span data-icon=""></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=""/></div>
|
||||
<div class="hidden-xs hidden-sm col-md-2 icon"><span data-icon=""></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=""/></div>
|
||||
<div class="hidden-xs hidden-sm col-md-2 icon"><span data-icon=""></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=""/></div>
|
||||
<div class="hidden-xs hidden-sm col-md-2 icon"><span data-icon=""></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=""/></div>
|
||||
<div class="hidden-xs hidden-sm col-md-2 icon"><span data-icon=""></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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 & 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>
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -1508,7 +1508,7 @@ ListView.Groups = Class.extend({
|
||||
// drag and drop enabled if list is not sorted (unless it is sorted by
|
||||
// its sequence field (ASC)), and there is a visible column with
|
||||
// @widget=handle or "sequence" column in the view.
|
||||
if ((dataset.sort && [seqname, seqname + 'ASC', ''].indexOf(dataset.sort()) === -1)
|
||||
if ((dataset.sort && [seqname, seqname + ' ASC', ''].indexOf(dataset.sort()) === -1)
|
||||
|| !_(this.columns).findWhere({'name': seqname})) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ var PivotView = View.extend({
|
||||
this.headers = {};
|
||||
this.cells = {};
|
||||
this.has_data = false;
|
||||
this.$buttons = $();
|
||||
|
||||
this.last_header_selected = null;
|
||||
this.sorted_column = {};
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user