[MERGE] forward port branch saas-17 up to f830f14d98

This commit is contained in:
Christophe Simonis
2017-07-20 13:26:09 +02:00
95 changed files with 1058 additions and 604 deletions
+15
View File
@@ -142,6 +142,11 @@ file_filter = addons/base_import_module/i18n/<lang>.po
source_file = addons/base_import_module/i18n/base_import_module.pot
source_lang = en
[odoo-master.base_sparse_field]
file_filter = addons/base_sparse_field/i18n/<lang>.po
source_file = addons/base_sparse_field/i18n/base_sparse_field.pot
source_lang = en
[odoo-master.base_setup]
file_filter = addons/base_setup/i18n/<lang>.po
source_file = addons/base_setup/i18n/base_setup.pot
@@ -892,6 +897,11 @@ file_filter = addons/website_sale/i18n/<lang>.po
source_file = addons/website_sale/i18n/website_sale.pot
source_lang = en
[odoo-master.website_sale_comparison]
file_filter = addons/website_sale_comparison/i18n/<lang>.po
source_file = addons/website_sale_comparison/i18n/website_sale_comparison.pot
source_lang = en
[odoo-master.website_sale_delivery]
file_filter = addons/website_sale_delivery/i18n/<lang>.po
source_file = addons/website_sale_delivery/i18n/website_sale_delivery.pot
@@ -912,6 +922,11 @@ file_filter = addons/website_sale_stock/i18n/<lang>.po
source_file = addons/website_sale_stock/i18n/website_sale_stock.pot
source_lang = en
[odoo-master.website_sale_wishlist]
file_filter = addons/website_sale_wishlist/i18n/<lang>.po
source_file = addons/website_sale_wishlist/i18n/website_sale_wishlist.pot
source_lang = en
[odoo-master.website_slides]
file_filter = addons/website_slides/i18n/<lang>.po
source_file = addons/website_slides/i18n/website_slides.pot
@@ -910,7 +910,7 @@ class AccountBankStatementLine(models.Model):
counterpart_moves = (counterpart_moves | move)
# Create The payment
payment = False
payment = self.env['account.payment']
if abs(total)>0.00001:
partner_id = self.partner_id and self.partner_id.id or False
partner_type = False
+10 -5
View File
@@ -773,7 +773,7 @@ class AccountInvoice(models.Model):
total_currency = 0
for line in invoice_move_lines:
if self.currency_id != company_currency:
currency = self.currency_id.with_context(date=self.date_invoice or fields.Date.context_today(self))
currency = self.currency_id.with_context(date=self.date or self.date_invoice or fields.Date.context_today(self))
if not (line.get('currency_id') and line.get('amount_currency')):
line['currency_id'] = currency.id
line['amount_currency'] = currency.round(line['price'])
@@ -907,7 +907,6 @@ class AccountInvoice(models.Model):
if not inv.date_invoice:
inv.with_context(ctx).write({'date_invoice': fields.Date.context_today(self)})
date_invoice = inv.date_invoice
company_currency = inv.company_id.currency_id
# create move lines (one per invoice line + eventual taxes and analytic lines)
@@ -920,9 +919,9 @@ 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=company_currency.id).compute(total, date_invoice)[0]
totlines = inv.with_context(ctx).payment_term_id.with_context(currency_id=company_currency.id).compute(total, inv.date_invoice)[0]
res_amount_currency = total_currency
ctx['date'] = date_invoice
ctx['date'] = inv.date or inv.date_invoice
for i, t in enumerate(totlines):
if inv.currency_id != company_currency:
amount_currency = company_currency.with_context(ctx).compute(t[1], inv.currency_id)
@@ -962,7 +961,7 @@ class AccountInvoice(models.Model):
journal = inv.journal_id.with_context(ctx)
line = inv.finalize_invoice_move_lines(line)
date = inv.date or date_invoice
date = inv.date or inv.date_invoice
move_vals = {
'ref': inv.reference,
'line_ids': line,
@@ -1578,6 +1577,12 @@ class AccountPaymentTerm(models.Model):
result.append((last_date, dist))
return result
@api.multi
def unlink(self):
property_recs = self.env['ir.property'].search([('value_reference', 'in', ['account.payment.term,%s'%payment_term.id for payment_term in self])])
property_recs.unlink()
return super(AccountPaymentTerm, self).unlink()
class AccountPaymentTermLine(models.Model):
_name = "account.payment.term.line"
+8 -2
View File
@@ -57,10 +57,16 @@ Best Regards,''')
if (date.month < last_month or (date.month == last_month and date.day <= last_day)):
date = date.replace(month=last_month, day=last_day)
else:
date = date.replace(month=last_month, day=last_day, year=date.year + 1)
if last_month == 2 and last_day == 29 and (date.year + 1) % 4 != 0:
date = date.replace(month=last_month, day=28, year=date.year + 1)
else:
date = date.replace(month=last_month, day=last_day, year=date.year + 1)
date_to = date
date_from = date + timedelta(days=1)
date_from = date_from.replace(year=date_from.year - 1)
if date_from.month == 2 and date_from.day == 29:
date_from = date_from.replace(day=28, year=date_from.year - 1)
else:
date_from = date_from.replace(year=date_from.year - 1)
return {'date_from': date_from, 'date_to': date_to}
def get_new_account_code(self, current_code, old_prefix, new_prefix, digits):
@@ -198,7 +198,7 @@ class ReportAgedPartnerBalance(models.AbstractModel):
@api.model
def get_report_values(self, docids, data=None):
if not data.get('form') or not self.env.context.get('active_model') or not self.env.context.get('active_id'):
raise UserError(_("Some data are missing, this report cannot be printed."))
raise UserError(_("Form content is missing, this report cannot be printed."))
total = []
model = self.env.context.get('active_model')
+1 -1
View File
@@ -61,7 +61,7 @@ class ReportTrialBalance(models.AbstractModel):
@api.model
def get_report_values(self, docids, data=None):
if not data.get('form') or not self.env.context.get('active_model'):
raise UserError(_("Some data are missing, this report cannot be printed."))
raise UserError(_("Form content is missing, this report cannot be printed."))
self.model = self.env.context.get('active_model')
docs = self.env[self.model].browse(self.env.context.get('active_ids', []))
@@ -111,7 +111,7 @@ class ReportGeneralLedger(models.AbstractModel):
@api.model
def get_report_values(self, docids, data=None):
if not data.get('form') or not self.env.context.get('active_model'):
raise UserError(_("Some data are missing, this report cannot be printed."))
raise UserError(_("Form content is missing, this report cannot be printed."))
self.model = self.env.context.get('active_model')
docs = self.env[self.model].browse(self.env.context.get('active_ids', []))
+1 -1
View File
@@ -97,7 +97,7 @@ class ReportJournal(models.AbstractModel):
@api.model
def get_report_values(self, docids, data=None):
if not data.get('form'):
raise UserError(_("Some data are missing, this report cannot be printed."))
raise UserError(_("Form content is missing, this report cannot be printed."))
target_move = data['form'].get('target_move', 'all')
sort_selection = data['form'].get('sort_selection', 'date')
@@ -71,7 +71,7 @@ class ReportPartnerLedger(models.AbstractModel):
@api.model
def get_report_values(self, docids, data=None):
if not data.get('form'):
raise UserError(_("Some data are missing, this report cannot be printed."))
raise UserError(_("Form content is missing, this report cannot be printed."))
data['computed'] = {}
@@ -147,7 +147,7 @@ class ReportFinancial(models.AbstractModel):
@api.model
def get_report_values(self, docids, data=None):
if not data.get('form') or not self.env.context.get('active_model') or not self.env.context.get('active_id'):
raise UserError(_("Some data are missing, this report cannot be printed."))
raise UserError(_("Form content is missing, this report cannot be printed."))
self.model = self.env.context.get('active_model')
docs = self.env[self.model].browse(self.env.context.get('active_id'))
+1 -1
View File
@@ -200,7 +200,7 @@
</p>
<div class="alert alert-info info_icon" role="alert">
<span class="fa fa-lightbulb-o fa-lg"/>
<p>Exchange rates can be automatically updated once a day from <strong>Yahoo Finance</strong> or the <strong>European Central Bank</strong>. You can activate this feature in the bottom of the <a t-att-href="prepare_backend_url('account.action_account_config')">Accounting Settings</a><a t-att-href="prepare_backend_url('account.action_account_config')">accounting settings</a>.</p>
<p>Exchange rates can be automatically updated once a day from <strong>Yahoo Finance</strong> or the <strong>European Central Bank</strong>. You can activate this feature in the bottom of the <a t-att-href="prepare_backend_url('account.action_account_config')">Accounting Settings</a>.</p>
</div>
</t>
<t t-call="web_planner.page">
@@ -223,7 +223,7 @@ class AccountBankStatementImport(models.TransientModel):
else:
ignored_statement_lines_import_ids.append(line_vals['unique_import_id'])
if 'balance_start' in st_vals:
st_vals['balance_start'] += line_vals['amount']
st_vals['balance_start'] += float(line_vals['amount'])
if len(filtered_st_lines) > 0:
# Remove values that won't be used to create records
+1 -1
View File
@@ -58,7 +58,7 @@ class ProviderGrid(models.Model):
price_unit = order.company_id.currency_id.with_context(date=order.date_order).compute(price_unit, order.pricelist_id.currency_id)
return {'success': True,
'price': self.fixed_price,
'price': price_unit,
'error_message': False,
'warning_message': False}
@@ -126,7 +126,7 @@
<t t-name="kanban-box">
<div class="oe_kanban_vignette oe_semantic_html_override">
<a type="open" href="#" class="oe_kanban_action">
<img t-att-src="kanban_image('fleet.vehicle.model.brand', 'image_small', record.id.value)" class="img-responsive" style="width:100px;"/>
<img t-att-src="kanban_image('fleet.vehicle.model.brand', 'image_small', record.id.raw_value)" class="img-responsive" style="width:100px;"/>
</a>
<div class="text-center">
<h4 class="oe_partner_heading">
+2 -2
View File
@@ -183,7 +183,7 @@
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
</div>
<div class="o_kanban_image">
<img t-att-src="kanban_image('fleet.vehicle', 'image_small', record.id.value)"/>
<img t-att-src="kanban_image('fleet.vehicle', 'image_small', record.id.raw_value)"/>
</div>
<div class="oe_kanban_details">
<strong>
@@ -397,4 +397,4 @@
<menuitem id="fleet_vehicle_tag_menu" parent="fleet_configuration" action="fleet_vehicle_tag_action" sequence="112"/>
</odoo>
</odoo>
+234 -68
View File
@@ -20,76 +20,242 @@
<record id="simple_report_template" model="mail.template">
<field name="name">Simple Challenge Report Progress</field>
<field name="model_id" ref="gamification.model_gamification_challenge"/>
<field name="body_html"><![CDATA[
<p>
<strong>${object.name}</strong>
</p>
<p class="oe_grey">The following message contains the current progress for the challenge ${object.name}</p>
% if object.visibility_mode == 'personal':
<table width="100%" border="1">
<tr>
<th>Goal</th>
<th>Target</th>
<th>Current</th>
<th>Completeness</th>
</tr>
% for line in ctx["challenge_lines"]:
<tr
% if line['completeness'] >= 100:
style="font-weight:bold;"
% endif
>
<td>${line['name']}</td>
<td>${line['target']}
% if line['suffix']:
${line['suffix']}
% endif
</td>
<td>${line['current']}
% if line['suffix']:
${line['suffix']}
% endif
</td>
<td>${line['completeness']} %</td>
</tr>
% endfor
</table>
% else:
% for line in ctx["challenge_lines"]:
<table width="100%" border="1">
<tr>
<th colspan="4">${line['name']}</th>
</tr>
<tr>
<th>#</th>
<th>Person</th>
<th>Completeness</th>
<th>Current</th>
</tr>
% for goal in line['goals']:
<tr
% if goal.completeness >= 100:
style="font-weight:bold;"
% endif
>
<td>${goal['rank']}</td>
<td>${goal['name']}</td>
<td>${goal['completeness']}%</td>
<td>${goal['current']}/${line['target']}
% if line['suffix']:
${line['suffix']}
% endif
</td>
</tr>
% endfor
<field name="body_html">
<![CDATA[
% set record = ctx.get('record')
% set company = record and record.company_id or user.company_id
% set challenge_lines = ctx.get('challenge_lines', [])
<table cellspacing="0" cellpadding="0" width="100%" style="font-family: 'Helvetica'; background-color: #EEE; border-collapse: collapse;">
<tr>
<td valign="top" align="center">
<table cellspacing="0" cellpadding="0" width="600" style="margin: 0 auto; width: 600px;">
<tr><td>
<table cellspacing="0" cellpadding="0" width="100%">
<tr>
<td valign="middle" align="left" style="padding:30px 15px;">
<img width="90" src="/logo.png?company=${user.company_id.id}" style="width:80px;"/>
</td>
% if object.visibility_mode == 'ranking':
<td style="padding:15px;">
<p style="font-size:20px;color:#666666;" align="right">Leaderboard</p>
</td>
% endif
</tr>
</table>
<table cellspacing="0" cellpadding="0" width="100%" bgcolor="#fff" style="background-color:#fff;">
<tr><td style="padding: 15px;">
% if object.visibility_mode == 'personal':
<span style="color:#666666;font-size:13px;">Here is your current progress in the challenge <strong>${object.name}</strong>.</span>
<table cellspacing="0" cellpadding="0" width="100%" style="margin-top:20px;">
<tr>
<td align="center">
<p style="font-size:27px;color:#666666;">Personal Performance</p>
</td>
</tr>
</table>
<table cellspacing="0" cellpadding="0" width="100%" style="margin-top:30px;color:#666666;">
<thead>
<tr style="color:#9A6C8E; font-size:12px;">
<th align="left" style="padding-bottom: 0px;width:40%;text-align:left;">Goals</th>
<th style="width:20%;text-align:right;" align="left">Target</th>
<th style="width:20%;text-align:right;" align="right">Current</th>
<th style="width:20%;text-align:right;" align="right">Completeness</th>
</tr>
<tr>
<td colspan="5" style="height:1px;background-color:#9A6C8E;"></td>
</tr>
</thead>
<tbody>
% for line in challenge_lines:
<tr
% if line['completeness'] >= 100:
style="font-weight:bold;"
% endif
>
<td style="padding: 20px 0;" align="left">
${line['name']}
% if line['suffix'] or line['monetary']:
(${line['full_suffix']})
% endif
</td>
<td style="padding: 20px 0;" align="right">${"%.2f" % line['target']}
% if line['suffix']:
${line['suffix']}
% endif
</td>
<td style="padding: 20px 0;" align="right">${"%.2f" % line['current']}
% if line['suffix']:
${line['suffix']}
% endif
</td>
<td style="padding: 20px 0;font-size:25px;color:#9A6C8E;" align="right"><strong>${line['completeness']| int}%</strong></td>
</tr>
<tr>
<td colspan="5" style="height:1px;background-color:#e3e3e3;"></td>
</tr>
% endfor
</tbody>
</table>
% else:
<span style="color:#A8A8A8;font-size:13px;">
The following message contains the current progress for the challenge <strong>${object.name}</strong>.
</span>
% for line in challenge_lines:
<!-- Header + Button table -->
<table cellspacing="0" cellpadding="0" width="100%"style="margin-top:35px;">
<tr>
<td width="50%">
<p style="font-size:22px;color:#666666;">Top Achievers for goal <strong>${line['name']}</strong></p>
</td>
</tr>
</table>
<!-- Podium -->
% if len(line['goals'])>2:
<table cellspacing="0" cellpadding="0" width="100%" style="margin-top:10px;">
<tr><td style="padding:0 30px;">
<table cellspacing="0" cellpadding="0" width="100%">
<tr>
% set top_goals = [line['goals'][1], line['goals'][0], line['goals'][2]]
% for goal in top_goals:
<td align="center" style="width:32%;">
% if loop.index == 1:
% set extra_div = '<div style="height:40px;"></div>'
% set heightA = 95
% set heightB = 75
% set bgColor = '#b898b0'
% set fontSize = 50
% set podiumPosition = '2'
% elif loop.index == 2:
% set extra_div = ''
% set heightA = 55
% set heightB = 115
% set bgColor = '#9A6C8E'
% set fontSize = 85
% set podiumPosition = '1'
% elif loop.index == 3:
% set extra_div = '<div style="height:60px;"></div>'
% set heightA = 115
% set heightB = 55
% set bgColor = '#c8afc1'
% set fontSize = 35
% set podiumPosition = '3'
% endif
<div style="margin:0 3px 0 3px;height:220px;">
<div style="height:${heightA}px;">
${extra_div | safe}
<div style="height:55px;">
<img style="margin-bottom:5px;width:50px;height:50px;border-radius:50%;" src="/web/image?model=res.users&amp;field=image_small&amp;id=${goal['user_id']}" alt=""/>
</div>
<div align="center" style ="color:${bgColor};">
${goal['name']}
</div
</div>
<div style="background-color:${bgColor | safe};height:${heightB}px;">
<strong><span style="color:#fff;font-size:${fontSize}px;">${podiumPosition | safe}</span></strong>
</div>
<div style="height:30px;">
<p style="color:#9A6C8E;font-size:20px;margin-top:10px;">
${"%.2f" % goal['current']}
% if line['suffix'] or line['monetary']:
${line['full_suffix']}
% endif
</p>
</div>
</div>
</td>
% endfor
</tr>
</table>
</td>
</tr>
</table>
% endif
<!-- data table -->
<table cellspacing="0" cellpadding="0" width="100%" style="margin-bottom:5px">
<tr>
<td>
<table cellspacing="0" cellpadding="0" width="100%" style="margin-top:30px;margin-bottom:5px;color:#666666;">
<thead>
<tr style="color:#9A6C8E; font-size:12px;">
<th style="width:10%;text-align:center;">Rank</th>
<th style="width:15%;text-align:left;">Name</th>
<th style="width:30%;text-align:right;">Performance
% if line['suffix']:
(${line['suffix']})
% elif line['monetary']:
(${company.currency_id.symbol})
% endif
</th>
<th style="width:30%;text-align:right;"">Completeness</th>
</tr>
<tr>
<td colspan="5" style="height:1px;background-color:#9A6C8E;"></td>
</tr>
</thead>
<tbody>
% for goal in line['goals']:
<tr>
% set tdBgColor = '#fff'
% set tdColor = 'gray'
% set mutedColor = '#AAAAAA'
% set tdPercentageColor = '#9A6C8E'
<td width="10%" align="center" valign="middle" style="background-color:${tdBgColor};padding :5px 0;font-size:20px;">${goal['rank']+1}
</td>
<td width="15%" align="left" valign="middle" style="background-color:${tdBgColor};padding :5px 0;font-size:13px;">${goal['name']}</td>
<td width="20%" align="right" style="background-color:${tdBgColor};padding:5px 0;line-height:1;">${"%.2f" % goal['current']}<br/><span style="font-size:13px;color:${mutedColor};">on ${"%.2f" % line['target']}</span>
</td>
<td width="20%" style="color:${tdPercentageColor};background-color:${tdBgColor};padding-right:15px;font-size:22px;" align="right"><strong>${goal['completeness'] | int}%</strong></td>
</tr>
<tr>
<td colspan="5" style="height:1px;background-color:#DADADA;"></td>
</tr>
% endfor
</tbody>
</table>
</td>
</tr>
</table>
% endfor
% endif
</td></tr>
</table>
<table cellspacing="0" cellpadding="0" width="100%" style="padding: 15px; color: #666666;margin-bottom: 5px;">
<tr>
<td align="center" style="min-width: 590px;">
<table width="580" border="0" cellpadding="0" style="min-width: 580px; font-size: 12px;">
<tr>
<td valign="middle" align="left" style="padding-top: 10px; padding-bottom: 10px;">
${company.name}<br/>
${company.phone or ''}
</td>
<td valign="middle" align="right" style="padding-top: 10px; padding-bottom: 10px;">
% if company.email:
<a href="mailto:${company.email}" style="text-decoration:none; color: #666666;">${company.email}</a><br/>
% endif
% if company.website:
<a href="${company.website}" style="text-decoration:none; color: #666666;">
${company.website}
</a>
% endif
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center">
Powered by <a target="_blank" href="https://www.odoo.com">Odoo</a>.
</td>
</tr>
</table>
</td></tr>
</table>
</td>
</tr>
</table>
]]>
<br/><br/>
% endfor
% endif
]]></field>
</field>
</record>
<!-- goal definitions -->
+1 -1
View File
@@ -177,7 +177,7 @@
<div class="oe_kanban_global_click">
<field name="category_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
<div class="o_kanban_image">
<img t-att-src="kanban_image('hr.employee', 'image_medium', record.id.value)"/>
<img t-att-src="kanban_image('hr.employee', 'image_medium', record.id.raw_value)"/>
<div class="o_employee_summary_icons">
<t t-if="record.message_needaction_counter.raw_value">
<span class='oe_kanban_mail_new' title='Unread Messages'><i class='fa fa-comments'/><t t-raw="record.message_needaction_counter.raw_value"/></span>
@@ -42,7 +42,7 @@
<t t-name="kanban-box">
<div class="oe_kanban_global_click">
<div class="o_kanban_image">
<img t-att-src="kanban_image('hr.employee', 'image_medium', record.id.value)"/>
<img t-att-src="kanban_image('hr.employee', 'image_medium', record.id.raw_value)"/>
</div>
<div class="oe_kanban_details">
<div id="textbox">
+1 -1
View File
@@ -44,7 +44,7 @@
<field name="name">Submitted</field>
<field name="res_model">hr.expense.sheet</field>
<field name="description">Expense report submitted, waiting approval</field>
<field name="default" eval="False"/>
<field name="default" eval="True"/>
</record>
<record id="mt_expense_paid" model="mail.message.subtype">
<field name="name">Paid</field>
+20 -10
View File
@@ -30,7 +30,8 @@ class HrExpense(models.Model):
company_id = fields.Many2one('res.company', string='Company', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.user.company_id)
currency_id = fields.Many2one('res.currency', string='Currency', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.user.company_id.currency_id)
analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, oldname='analytic_account')
account_id = fields.Many2one('account.account', string='Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, default=lambda self: self.env['ir.property'].get('property_account_expense_categ_id', 'product.category'))
account_id = fields.Many2one('account.account', string='Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, default=lambda self: self.env['ir.property'].get('property_account_expense_categ_id', 'product.category'),
help="An expense account is expected")
description = fields.Text()
payment_mode = fields.Selection([("own_account", "Employee (to reimburse)"), ("company_account", "Company")], default='own_account', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, string="Payment By")
attachment_number = fields.Integer(compute='_compute_attachment_number', string='Number of Attachments')
@@ -419,10 +420,15 @@ class HrExpenseSheet(models.Model):
@api.model
def create(self, vals):
# Add the followers at creation, so they can be notified
if vals.get('employee_id'):
employee = self.env['hr.employee'].browse(vals['employee_id'])
users = self._get_users_to_subscribe(employee=employee) - self.env.user
vals['message_follower_ids'] = []
for partner in users.mapped('partner_id'):
vals['message_follower_ids'] += self.env['mail.followers']._add_follower_command(self._name, [], {partner.id: None}, {})[0]
sheet = super(HrExpenseSheet, self).create(vals)
self.check_consistency()
if vals.get('employee_id'):
sheet._add_followers()
return sheet
@api.multi
@@ -457,16 +463,20 @@ class HrExpenseSheet(models.Model):
return 'hr_expense.mt_expense_paid'
return super(HrExpenseSheet, self)._track_subtype(init_values)
def _add_followers(self):
user_ids = []
employee = self.employee_id
def _get_users_to_subscribe(self, employee=False):
users = self.env['res.users']
employee = employee or self.employee_id
if employee.user_id:
user_ids.append(employee.user_id.id)
users |= employee.user_id
if employee.parent_id:
user_ids.append(employee.parent_id.user_id.id)
users |= employee.parent_id.user_id
if employee.department_id and employee.department_id.manager_id and employee.parent_id != employee.department_id.manager_id:
user_ids.append(employee.department_id.manager_id.user_id.id)
self.message_subscribe_users(user_ids=user_ids)
users |= employee.department_id.manager_id.user_id
return users
def _add_followers(self):
users = self._get_users_to_subscribe()
self.message_subscribe_users(user_ids=users.ids)
@api.onchange('employee_id')
def _onchange_employee_id(self):
+1 -1
View File
@@ -60,7 +60,7 @@
</group><group>
<field name="reference"/>
<field name="date"/>
<field name="account_id"/>
<field name="account_id" domain="[('internal_type', '=', 'other')]"/>
<field name="employee_id"/>
<field name="sheet_id" invisible="1"/>
<field name="currency_id" groups="base.group_multi_currency"/>
@@ -109,7 +109,7 @@ class HrHolidaySummaryReport(models.AbstractModel):
@api.model
def get_report_values(self, docids, data=None):
if not data.get('form'):
raise UserError(_("Some data are missing, this report cannot be printed."))
raise UserError(_("Form content is missing, this report cannot be printed."))
holidays_report = self.env['ir.actions.report']._get_report_from_name('hr_holidays.report_holidayssummary')
holidays = self.env['hr.holidays'].browse(self.ids)
@@ -31,7 +31,7 @@ class ContributionRegisterReport(models.AbstractModel):
@api.model
def get_report_values(self, docids, data=None):
if not data.get('form'):
raise UserError(_("Some data are missing, this report cannot be printed."))
raise UserError(_("Form content is missing, this report cannot be printed."))
register_ids = self.env.context.get('active_ids', [])
contrib_registers = self.env['hr.contribution.register'].browse(register_ids)
@@ -36,7 +36,7 @@
<t t-name="kanban-box">
<div class="oe_kanban_global_click">
<div class="o_kanban_image">
<img t-att-src="kanban_image('im_livechat.channel', 'image_medium', record.id.value)" class="img-responsive"/>
<img t-att-src="kanban_image('im_livechat.channel', 'image_medium', record.id.raw_value)" class="img-responsive"/>
</div>
<div class="oe_kanban_details">
<strong><field name="name"/></strong>
@@ -108,7 +108,7 @@
<div>
<a t-if="! read_only_mode" type="delete" style="position: absolute; right: 0; padding: 4px; diplay: inline-block">X</a>
<div class="oe_group_details" style="min-height: 40px">
<img t-att-src="kanban_image('res.users', 'image', record.id.value)"
<img t-att-src="kanban_image('res.users', 'image', record.id.raw_value)"
class="oe_avatar oe_kanban_avatar_smallbox" style="float:left; margin-right: 10px;"/>
<h4><field name="name"/></h4>
</div>
@@ -2928,6 +2928,7 @@
<field name="name">Coupons échus à l'encaissement</field>
<field name="code">5111</field>
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
<field name="reconcile" eval="True"/>
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
</record>
@@ -2935,6 +2936,7 @@
<field name="name">Chèques à encaisser</field>
<field name="code">5112</field>
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
<field name="reconcile" eval="True"/>
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
</record>
@@ -2942,6 +2944,7 @@
<field name="name">Effets à l'encaissement</field>
<field name="code">5113</field>
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
<field name="reconcile" eval="True"/>
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
</record>
@@ -2949,6 +2952,7 @@
<field name="name">Effets à l'escompte</field>
<field name="code">5114</field>
<field name="user_type_id" ref="account.data_account_type_liquidity"/>
<field name="reconcile" eval="True"/>
<field name="chart_template_id" ref="l10n_fr_pcg_chart_template"/>
</record>
@@ -95,7 +95,7 @@ class ReportHrSalaryEmployeeBymonth(models.AbstractModel):
@api.model
def get_report_values(self, docids, data=None):
if not data.get('form') or not self.env.context.get('active_model') or not self.env.context.get('active_id'):
raise UserError(_("Some data are missing, this report cannot be printed."))
raise UserError(_("Form content is missing, this report cannot be printed."))
model = self.env.context.get('active_model')
docs = self.env[model].browse(self.env.context.get('active_id'))
@@ -130,7 +130,7 @@ class EmployeesYearlySalaryReport(models.AbstractModel):
@api.model
def get_report_values(self, docids, data=None):
if not self.env.context.get('active_model') or not self.env.context.get('active_id'):
raise UserError(_("Some data are missing, this report cannot be printed."))
raise UserError(_("Form content is missing, this report cannot be printed."))
model = self.env.context.get('active_model')
docs = self.env[model].browse(self.env.context.get('active_id'))
+1 -1
View File
@@ -61,7 +61,7 @@
</t>
<t t-name="kanban-box">
<div class="oe_module_vignette oe_kanban_global_click">
<img t-att-src="kanban_image('mail.channel', 'image_medium', record.id.value)" class="oe_module_icon"/>
<img t-att-src="kanban_image('mail.channel', 'image_medium', record.id.raw_value)" class="oe_module_icon"/>
<div class="oe_module_desc">
<h4>#<field name="name"/></h4>
<p class="oe_module_name">
+3 -4
View File
@@ -60,15 +60,14 @@ class MailMail(models.Model):
self.ensure_one()
body = super(MailMail, self).send_get_mail_body(partner=partner)
links_blacklist = ['/unsubscribe_from_list']
if self.mailing_id and body and self.statistics_ids:
for match in re.findall(URL_REGEX, self.body_html):
href = match[0]
url = match[1]
if not [s for s in links_blacklist if s in href]:
parsed = urlparse.urlparse(url, scheme='http')
if parsed.scheme.startswith('http') and parsed.path.startswith('/r/'):
new_href = href.replace(url, url + '/m/' + str(self.statistics_ids[0].id))
body = body.replace(href, new_href)
+10
View File
@@ -39,3 +39,13 @@ class MailThread(models.AbstractModel):
message_ids = [x.strip() for x in decode_smtp_header(message['References']).split()]
self.env['mail.mail.statistics'].set_replied(mail_message_ids=message_ids)
return super(MailThread, self).message_route_process(message, message_dict, routes)
@api.multi
def message_post_with_template(self, template_id, **kwargs):
# avoid having message send through `message_post*` methods being implicitly considered as
# mass-mailing
no_massmail = self.with_context(
default_mass_mailing_name=False,
default_mass_mailing_id=False,
)
return super(MailThread, no_massmail).message_post_with_template(template_id, **kwargs)
+6 -5
View File
@@ -263,11 +263,12 @@ class Repair(models.Model):
return self.env.ref('mrp_repair.action_report_mrp_repair_order').report_action(self)
def action_repair_invoice_create(self):
self.action_invoice_create()
if self.invoice_method == 'b4repair':
self.action_repair_ready()
elif self.invoice_method == 'after_repair':
self.write({'state': 'done'})
for repair in self:
repair.action_invoice_create()
if repair.invoice_method == 'b4repair':
repair.action_repair_ready()
elif repair.invoice_method == 'after_repair':
repair.write({'state': 'done'})
return True
@api.multi
+1 -1
View File
@@ -30,7 +30,7 @@ class EtherpadLiteClient:
params = arguments or {}
params['apikey'] = self.apiKey
r = requests.get(url, data=params, timeout=self.TIMEOUT)
r = requests.post(url, data=params, timeout=self.TIMEOUT)
r.raise_for_status()
return self.handleResult(r.json())
+1 -1
View File
@@ -140,7 +140,7 @@
<t t-set="installed" t-value="!record.module_id.value || (record.module_id.value &amp;&amp; record.module_state.raw_value === 'installed')"/>
<div t-attf-class="oe_kanban_global_click #{installed and record.website_published.raw_value ? 'oe_kanban_color_5' : 'oe_kanban_color_white'}">
<div class="o_kanban_image">
<img type="open" t-att-src="kanban_image('payment.acquirer', 'image_small', record.id.value)"/>
<img type="open" t-att-src="kanban_image('payment.acquirer', 'image_small', record.id.raw_value)"/>
</div>
<div class="o_payment_acquirer_desc">
<h3 class="mt4"><t t-esc="record.name.value"/></h3>
+2 -2
View File
@@ -256,14 +256,14 @@ class TxAuthorize(models.Model):
def authorize_s2s_capture_transaction(self):
self.ensure_one()
transaction = AuthorizeAPI(self.acquirer_id)
tree = transaction.capture(self.acquirer_reference, self.amount)
tree = transaction.capture(self.acquirer_reference or '', self.amount)
return self._authorize_s2s_validate_tree(tree)
@api.multi
def authorize_s2s_void_transaction(self):
self.ensure_one()
transaction = AuthorizeAPI(self.acquirer_id)
tree = transaction.void(self.acquirer_reference)
tree = transaction.void(self.acquirer_reference or '')
return self._authorize_s2s_validate_tree(tree)
@api.multi
+1 -1
View File
@@ -649,7 +649,7 @@ class PosOrder(models.Model):
return_picking = Picking.create(return_vals)
return_picking.message_post(body=message)
for line in order.lines.filtered(lambda l: l.product_id.type in ['product', 'consu'] and l.qty != 0):
for line in order.lines.filtered(lambda l: l.product_id.type in ['product', 'consu'] and not float_is_zero(l.qty, precision_digits=l.product_id.uom_id.rounding)):
moves |= Move.create({
'name': line.name,
'product_uom': line.product_id.uom_id.id,
+1 -1
View File
@@ -296,7 +296,7 @@ class PosSession(models.Model):
if not self.ids:
return {}
for session in self.filtered(lambda s: s.user_id.id != self.env.uid):
raise UserError(_("You cannot use the session of another users. This session is owned by %s. "
raise UserError(_("You cannot use the session of another user. This session is owned by %s. "
"Please first close this one to use this point of sale.") % session.user_id.name)
return {
'type': 'ir.actions.act_url',
+1 -1
View File
@@ -270,7 +270,7 @@ var DebugWidget = PosBaseWidget.extend({
this.$('.button.delete_unpaid_orders').click(function(){
self.gui.show_popup('confirm',{
'title': _t('Delete Unpaid Orders ?'),
'body': _t('This operation will permanently destroy all unpaid orders from all sessions that have been put in the local storage. You will lose all the data and exit the point of sale. This operation cannot be undone.'),
'body': _t('This operation will destroy all unpaid orders in the browser. You will lose all the unsaved data and exit the point of sale. This operation cannot be undone.'),
confirm: function(){
self.pos.db.remove_all_unpaid_orders();
window.location = '/';
@@ -33,7 +33,7 @@
<filter string="Invoiced" domain="[('state','=',('invoiced'))]"/>
<filter string="Not Invoiced" domain="[('state','=',('paid'))]"/>
<separator/>
<filter string="Year" name="year" domain="[('date','&lt;=', time.strftime('%%Y-12-31')),('date','&gt;=',time.strftime('%%Y-01-01'))]" help="POS ordered created during current year"/>
<filter string="Year" name="year" domain="[('date','&lt;=', time.strftime('%%Y-12-31')),('date','&gt;=',time.strftime('%%Y-01-01'))]" help="POS orders created during the current year"/>
<separator/>
<filter string="My Sales" help="My Sales" domain="[('user_id','=',uid)]"/>
<field name="config_id"/>
@@ -75,7 +75,7 @@
<t t-name="kanban-box">
<div class="oe_kanban_global_click">
<div class="o_kanban_image">
<img t-att-src="kanban_image('product.template', 'image_small', record.id.value)"/>
<img t-att-src="kanban_image('product.template', 'image_small', record.id.raw_value)"/>
</div>
<div class="oe_kanban_details">
<strong>
+1 -1
View File
@@ -349,7 +349,7 @@
<field name="attribute_value_ids" groups="product.group_product_variant"/>
</div>
<div class="o_kanban_image">
<img t-att-src="kanban_image('product.product', 'image_small', record.id.value)"/>
<img t-att-src="kanban_image('product.product', 'image_small', record.id.raw_value)"/>
</div>
<div class="oe_kanban_details">
<strong><field name="name"/></strong>
+1 -1
View File
@@ -650,7 +650,7 @@ class PurchaseOrderLine(models.Model):
move.state = 'draft'
move.unlink()
else:
raise UserError('You cannot decrease the ordered quantity below your receipt.\n'
raise UserError('You cannot decrease the ordered quantity below the received quantity.\n'
'Create a return first.')
@api.multi
+4 -11
View File
@@ -12,27 +12,20 @@ class SaleOrderLine(models.Model):
@api.multi
def _compute_analytic(self, domain=None):
lines = {}
force_so_lines = self.env.context.get("force_so_lines")
if not domain:
if not self.ids and not force_so_lines:
return True
# To filter on analyic lines linked to an expense
expense_type_id = self.env.ref('account.data_account_type_expenses', raise_if_not_found=False)
expense_type_id = expense_type_id and expense_type_id.id
domain = [
('so_line', 'in', self.ids),
'|',
('amount', '<', 0),
'&',
('amount', '=', 0),
'|',
('move_id', '=', False),
('move_id.account_id.user_type_id', '=', expense_type_id)
]
domain = [('so_line', 'in', self.ids), ('amount', '<=', 0.0)]
data = self.env['account.analytic.line'].read_group(
domain,
['so_line', 'unit_amount', 'product_uom_id'], ['product_uom_id', 'so_line'], lazy=False
)
# If the unlinked analytic line was the last one on the SO line, the qty was not updated.
force_so_lines = self.env.context.get("force_so_lines")
if force_so_lines:
for line in force_so_lines:
lines.setdefault(line, 0.0)
+2 -1
View File
@@ -138,7 +138,7 @@ class SaleConfigSettings(models.TransientModel):
use_sale_note=self.env['ir.config_parameter'].sudo().get_param('sale.use_sale_note', default=False),
auto_done_setting=self.env['ir.config_parameter'].sudo().get_param('sale.auto_done_setting'),
default_deposit_product_id=self.env['ir.config_parameter'].sudo().get_param('sale.default_deposit_product_id'),
sale_show_tax=self.group_show_price_total and 'total' or 'subtotal',
sale_show_tax=self.env['ir.config_parameter'].sudo().get_param('sale.sale_show_tax', default='subtotal'),
multi_sales_price=sale_pricelist_setting in ['percentage', 'formula'],
multi_sales_price_method=sale_pricelist_setting in ['percentage', 'formula'] and sale_pricelist_setting or False,
sale_pricelist_setting=sale_pricelist_setting,
@@ -152,3 +152,4 @@ class SaleConfigSettings(models.TransientModel):
self.env['ir.config_parameter'].sudo().set_param("sale.auto_done_setting", self.auto_done_setting)
self.env['ir.config_parameter'].sudo().set_param("sale.default_deposit_product_id", self.default_deposit_product_id.id)
self.env['ir.config_parameter'].sudo().set_param('sale.sale_pricelist_setting', self.sale_pricelist_setting)
self.env['ir.config_parameter'].sudo().set_param('sale.sale_show_tax', self.sale_show_tax)
@@ -113,6 +113,7 @@ class SaleAdvancePaymentInv(models.TransientModel):
'payment_term_id': order.payment_term_id.id,
'fiscal_position_id': order.fiscal_position_id.id or order.partner_id.property_account_position_id.id,
'team_id': order.team_id.id,
'user_id': order.user_id.id,
'comment': order.note,
})
invoice.compute_taxes()
+2 -13
View File
@@ -140,20 +140,9 @@ class SaleOrderLine(models.Model):
@api.multi
def _compute_analytic(self, domain=None):
if not domain:
if not domain and self.ids:
# To filter on analyic lines linked to an expense
expense_type_id = self.env.ref('account.data_account_type_expenses', raise_if_not_found=False)
expense_type_id = expense_type_id and expense_type_id.id
domain = [
('so_line', 'in', self.ids),
'|',
'|',
('amount', '<', 0.0),
('project_id', '!=', False),
'&',
('amount', '=', 0),
'|',
('move_id', '=', False),
('move_id.account_id.user_type_id', '=', expense_type_id)
]
domain = [('so_line', 'in', self.ids), '|', ('amount', '<=', 0.0), ('project_id', '!=', False)]
return super(SaleOrderLine, self)._compute_analytic(domain=domain)
+1 -1
View File
@@ -105,7 +105,7 @@
<div class="oe_kanban_global_click" style="position: relative">
<a t-if="! read_only_mode" type="delete" style="position: absolute; right: 0; padding: 4px; diplay: inline-block">X</a>
<div class="oe_module_vignette">
<img t-att-src="kanban_image('res.users', 'image_small', record.id.value)" class="oe_avatar oe_kanban_avatar_smallbox"/>
<img t-att-src="kanban_image('res.users', 'image_small', record.id.raw_value)" class="oe_avatar oe_kanban_avatar_smallbox"/>
<div class="oe_module_desc">
<field name="name"/>
</div>
+6 -22
View File
@@ -21,8 +21,6 @@ OPERATORS = {
class Product(models.Model):
_inherit = "product.product"
reception_count = fields.Integer('Receipt', compute='_compute_reception_count')
delivery_count = fields.Integer('Delivery', compute='_compute_delivery_count')
stock_quant_ids = fields.One2many('stock.quant', 'product_id', help='Technical: used to compute quantities.')
stock_move_ids = fields.One2many('stock.move', 'product_id', help='Technical: used to compute quantities.')
qty_available = fields.Float(
@@ -78,26 +76,6 @@ class Product(models.Model):
reordering_min_qty = fields.Float(compute='_compute_nbr_reordering_rules')
reordering_max_qty = fields.Float(compute='_compute_nbr_reordering_rules')
def _compute_reception_count(self):
move_data = self.env['stock.move'].read_group([
('product_id', 'in', self.ids),
('location_id.usage', '!=', 'internal'),
('location_dest_id.usage', '=', 'internal'),
('state', 'in', ('confirmed', 'assigned', 'pending'))], ['product_id'], ['product_id'])
res = dict((data['product_id'][0], data['product_id_count']) for data in move_data)
for move in self:
move.reception_count = res.get(move.id, 0)
def _compute_delivery_count(self):
move_data = self.env['stock.move'].read_group([
('product_id', 'in', self.ids),
('location_id.usage', '=', 'internal'),
('location_dest_id.usage', '!=', 'internal'),
('state', 'in', ('confirmed', 'assigned', 'pending'))], ['product_id'], ['product_id'])
res = dict((data['product_id'][0], data['product_id_count']) for data in move_data)
for move in self:
move.delivery_count = res.get(move.id, 0)
@api.depends('stock_quant_ids', 'stock_move_ids')
def _compute_quantities(self):
res = self._compute_quantities_dict(self._context.get('lot_id'), self._context.get('owner_id'), self._context.get('package_id'), self._context.get('from_date'), self._context.get('to_date'))
@@ -365,6 +343,12 @@ class Product(models.Model):
def action_view_routes(self):
return self.mapped('product_tmpl_id').action_view_routes()
@api.multi
def write(self, values):
res = super(Product, self).write(values)
if 'active' in values and not values['active'] and self.mapped('orderpoint_ids').filtered(lambda r: r.active):
raise UserError(_('You still have some active reordering rules on this product. Please archive or delete them first.'))
return res
class ProductTemplate(models.Model):
_inherit = 'product.template'
+12 -10
View File
@@ -408,19 +408,21 @@ class InventoryLine(models.Model):
'company_id': self.inventory_id.company_id.id,
'inventory_id': self.inventory_id.id,
'state': 'confirmed',
#'restrict_lot_id': self.prod_lot_id.id,
'restrict_partner_id': self.partner_id.id,
'location_id': location_id,
'location_dest_id': location_dest_id,
'move_line_ids': [(0, 0, {'product_id': self.product_id.id,
'lot_id': self.prod_lot_id.id,
'product_uom_qty': 0, # bypass reservation here
'product_uom_id': self.product_uom_id.id,
'qty_done': qty,
'package_id': out and self.package_id.id or False,
'result_package_id': (not out) and self.package_id.id or False,
'location_id': location_id, #TODO: owner stuff
'location_dest_id': location_dest_id,})]
'move_line_ids': [(0, 0, {
'product_id': self.product_id.id,
'lot_id': self.prod_lot_id.id,
'product_uom_qty': 0, # bypass reservation here
'product_uom_id': self.product_uom_id.id,
'qty_done': qty,
'package_id': out and self.package_id.id or False,
'result_package_id': (not out) and self.package_id.id or False,
'location_id': location_id,
'location_dest_id': location_dest_id,
'owner_id': self.partner_id.id,
})]
}
def _generate_moves(self):
+9 -12
View File
@@ -52,7 +52,7 @@ class StockMove(models.Model):
product_uom_qty = fields.Float(
'Initial Demand',
digits=dp.get_precision('Product Unit of Measure'),
default=1.0, required=True, states={'done': [('readonly', True)]},
default=0.0, required=True, states={'done': [('readonly', True)]},
help="This is the quantity of products from an inventory "
"point of view. For moves in the state 'done', this is the "
"quantity of products that were actually moved. For other "
@@ -144,7 +144,6 @@ class StockMove(models.Model):
string_availability_info = fields.Text(
'Availability', compute='_compute_string_qty_information',
readonly=True, help='Show various information on stock availability for this move')
restrict_lot_id = fields.Many2one('stock.production.lot', 'Lot/Serial Number', help="Technical field used to depict a restriction on the lot/serial number of quants to consider when marking this move as 'done'")
restrict_partner_id = fields.Many2one('res.partner', 'Owner ', help="Technical field used to depict a restriction on the ownership of quants to consider when marking this move as 'done'")
route_ids = fields.Many2many('stock.location.route', 'stock_location_route_move', 'move_id', 'route_id', 'Destination route', help="Preferred route to be followed by the procurement order")
warehouse_id = fields.Many2one('stock.warehouse', 'Warehouse', help="Technical field depicting the warehouse to consider for the route selection on the next procurement (if any).")
@@ -179,9 +178,10 @@ class StockMove(models.Model):
if self.user_has_groups('stock.group_stock_multi_locations'):
multi_locations_enabled = move.location_id.child_ids or move.location_dest_id.child_ids
has_package = move.move_line_ids.mapped('package_id') | move.move_line_ids.mapped('result_package_id')
consignment_enabled = self.user_has_groups('stock.group_tracking_owner')
if move.picking_id.picking_type_id.show_operations is False\
and move.state not in ['cancel', 'draft', 'confirmed']\
and (multi_locations_enabled or move.has_tracking != 'none' or len(move.move_line_ids) > 1 or has_package):
and (multi_locations_enabled or move.has_tracking != 'none' or len(move.move_line_ids) > 1 or has_package or consignment_enabled):
move.show_details_visible = True
else:
move.show_details_visible = False
@@ -498,8 +498,6 @@ class StockMove(models.Model):
product = self.product_id.with_context(lang=self.partner_id.lang or self.env.user.lang)
self.name = product.partner_ref
self.product_uom = product.uom_id.id
if self.product_uom_qty:
self.product_uom_qty = 1.0
return {'domain': {'product_uom': [('category_id', '=', product.uom_id.category_id.id)]}}
@api.onchange('date')
@@ -646,7 +644,7 @@ class StockMove(models.Model):
if move.picking_id and \
(move.picking_id.picking_type_id.use_existing_lots or move.picking_id.picking_type_id.use_create_lots) and \
move.product_id.tracking != 'none' and \
not (move.restrict_lot_id or (move_line and (move_line.product_id and move_line.pack_lot_ids)) or (move_line and not move_line.product_id)):
not (move_line and (move_line.product_id and move_line.pack_lot_ids)) or (move_line and not move_line.product_id):
raise UserError(_('You need to provide a Lot/Serial Number for product %s') % move.product_id.name)
def _prepare_move_line_vals(self, quantity=None, reserved_quant=None):
@@ -674,7 +672,7 @@ class StockMove(models.Model):
)
return vals
def _increase_reserved_quantity(self, need, available_quantity, location_id, lot_id=None, package_id=None, owner_id=None, strict=True):
def _update_reserved_quantity(self, need, available_quantity, location_id, lot_id=None, package_id=None, owner_id=None, strict=True):
""" Create or update move lines.
"""
self.ensure_one()
@@ -689,7 +687,7 @@ class StockMove(models.Model):
taken_quantity = min(available_quantity, need)
# Find a candidate move line to update or create a new one.
quants = self.env['stock.quant']._increase_reserved_quantity(
quants = self.env['stock.quant']._update_reserved_quantity(
self.product_id, location_id, taken_quantity, lot_id=lot_id,
package_id=package_id, owner_id=owner_id, strict=strict
)
@@ -733,7 +731,7 @@ class StockMove(models.Model):
if available_quantity <= 0:
continue
need = move.product_qty - move.reserved_availability
taken_quantity = move._increase_reserved_quantity(need, available_quantity, move.location_id, strict=False)
taken_quantity = move._update_reserved_quantity(need, available_quantity, move.location_id, strict=False)
if need == taken_quantity:
move.state = 'assigned'
else:
@@ -770,7 +768,7 @@ class StockMove(models.Model):
available_move_lines[(move_line.location_id, move_line.lot_id, move_line.result_package_id, move_line.owner_id)] -= move_line.product_qty
for (location_id, lot_id, package_id, owner_id), quantity in available_move_lines.items():
need = move.product_qty - sum(move.move_line_ids.mapped('product_qty'))
taken_quantity = move._increase_reserved_quantity(need, quantity, location_id, lot_id, package_id, owner_id)
taken_quantity = move._update_reserved_quantity(need, quantity, location_id, lot_id, package_id, owner_id)
if need - taken_quantity == 0.0:
move.state = 'assigned'
break
@@ -931,11 +929,10 @@ class StockMove(models.Model):
return vals
@api.multi
def split(self, qty, restrict_lot_id=False, restrict_partner_id=False):
def split(self, qty, restrict_partner_id=False):
""" Splits qty from move move into a new move
:param qty: float. quantity to split (given in product UoM)
:param restrict_lot_id: optional production lot that can be given in order to force the new move to restrict its choice of quants to this lot.
:param restrict_partner_id: optional partner that can be given in order to force the new move to restrict its choice of quants to the ones belonging to this partner.
:param context: dictionay. can contains the special key 'source_location_id' in order to force the source location when copying the move
:returns: id of the backorder move created """
+23 -23
View File
@@ -99,16 +99,16 @@ class StockMoveLine(models.Model):
Quant = self.env['stock.quant']
quantity = ml.product_uom_id._compute_quantity(ml.qty_done, ml.move_id.product_id.uom_id,rounding_method='HALF-UP')
if ml.location_id.should_impact_quants() and ml.product_id.type == 'product':
available_qty = Quant._decrease_available_quantity(ml.product_id, ml.location_id, quantity, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
available_qty = Quant._update_available_quantity(ml.product_id, ml.location_id, -quantity, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
if available_qty < 0 and ml.lot_id:
# see if we can compensate the negative quants with some untracked quants
untracked_qty = Quant._get_available_quantity(ml.product_id, ml.location_id, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
if untracked_qty:
taken_from_untracked_qty = min(untracked_qty, abs(quantity))
Quant._decrease_available_quantity(ml.product_id, ml.location_id, taken_from_untracked_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id)
Quant._increase_available_quantity(ml.product_id, ml.location_id, taken_from_untracked_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
Quant._update_available_quantity(ml.product_id, ml.location_id, -taken_from_untracked_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id)
Quant._update_available_quantity(ml.product_id, ml.location_id, taken_from_untracked_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
if ml.location_dest_id.should_impact_quants() and ml.product_id.type == 'product':
Quant._increase_available_quantity(ml.product_id, ml.location_dest_id, quantity, lot_id=ml.lot_id, package_id=ml.result_package_id, owner_id=ml.owner_id)
Quant._update_available_quantity(ml.product_id, ml.location_dest_id, quantity, lot_id=ml.lot_id, package_id=ml.result_package_id, owner_id=ml.owner_id)
next_moves = ml.move_id.move_dest_ids.filtered(lambda move: move.state not in ('done', 'cancel'))
next_moves.do_unreserve()
next_moves.action_assign()
@@ -132,10 +132,10 @@ class StockMoveLine(models.Model):
if ml.location_id.should_impact_quants() and ml.product_id.type == 'product':
qty_to_decrease = ml.product_qty - ml.product_uom_id._compute_quantity(vals['product_uom_qty'], ml.product_id.uom_id, rounding_method='HALF-UP')
try:
Quant._decrease_reserved_quantity(ml.product_id, ml.location_id, qty_to_decrease, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -qty_to_decrease, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
except UserError:
if ml.lot_id:
Quant._decrease_reserved_quantity(ml.product_id, ml.location_id, qty_to_decrease, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id)
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -qty_to_decrease, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id)
else:
raise
@@ -156,24 +156,24 @@ class StockMoveLine(models.Model):
for ml in self.filtered(lambda ml: ml.state in ['partially_available', 'assigned']):
if ml.location_id.should_impact_quants() and ml.product_id.type == 'product':
try:
Quant._decrease_reserved_quantity(ml.product_id, ml.location_id, ml.product_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
except UserError:
if ml.lot_id:
Quant._decrease_reserved_quantity(ml.product_id, ml.location_id, ml.product_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id)
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id)
else:
raise
if updates.get('location_id', ml.location_id).should_impact_quants() and ml.product_id.type == 'product':
new_product_qty = 0
try:
q = Quant._increase_reserved_quantity(ml.product_id, updates.get('location_id', ml.location_id), ml.product_qty, lot_id=updates.get('lot_id', ml.lot_id),
q = Quant._update_reserved_quantity(ml.product_id, updates.get('location_id', ml.location_id), ml.product_qty, lot_id=updates.get('lot_id', ml.lot_id),
package_id=updates.get('package_id', ml.package_id), owner_id=updates.get('owner_id', ml.owner_id), strict=True)
new_product_qty = sum([x[1] for x in q])
except UserError:
if updates.get('lot_id'):
# If we were not able to reserve on tracked quants, we can use untracked ones.
try:
q = Quant._increase_reserved_quantity(ml.product_id, updates.get('location_id', ml.location_id), ml.product_qty, lot_id=False,
q = Quant._update_reserved_quantity(ml.product_id, updates.get('location_id', ml.location_id), ml.product_qty, lot_id=False,
package_id=updates.get('package_id', ml.package_id), owner_id=updates.get('owner_id', ml.owner_id), strict=True)
new_product_qty = sum([x[1] for x in q])
except UserError:
@@ -188,10 +188,10 @@ class StockMoveLine(models.Model):
for ml in self.filtered(lambda ml: ml.move_id.state == 'done'):
# undo the original move line
if ml.location_dest_id.should_impact_quants() and ml.product_id.type == 'product':
Quant._decrease_available_quantity(ml.product_id, ml.location_dest_id, ml.qty_done, lot_id=ml.lot_id,
Quant._update_available_quantity(ml.product_id, ml.location_dest_id, -ml.qty_done, lot_id=ml.lot_id,
package_id=ml.package_id, owner_id=ml.owner_id)
if ml.location_id.should_impact_quants() and ml.product_id.type == 'product':
Quant._increase_available_quantity(ml.product_id, ml.location_id, ml.qty_done, lot_id=ml.lot_id,
Quant._update_available_quantity(ml.product_id, ml.location_id, ml.qty_done, lot_id=ml.lot_id,
package_id=ml.package_id, owner_id=ml.owner_id)
# move what's been actually done
@@ -206,17 +206,17 @@ class StockMoveLine(models.Model):
quantity = ml.move_id.product_uom._compute_quantity(qty_done, ml.move_id.product_id.uom_id, rounding_method='HALF-UP')
if location_id.should_impact_quants() and ml.product_id.type == 'product':
ml._free_reservation(product_id, location_id, quantity, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
available_qty = Quant._decrease_available_quantity(product_id, location_id, quantity, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
available_qty = Quant._update_available_quantity(product_id, location_id, -quantity, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
if available_qty < 0 and lot_id:
# see if we can compensate the negative quants with some untracked quants
untracked_qty = Quant._get_available_quantity(product_id, location_id, lot_id=False, package_id=package_id, owner_id=owner_id, strict=True)
if untracked_qty:
taken_from_untracked_qty = min(untracked_qty, abs(available_qty))
Quant._decrease_available_quantity(product_id, location_id, taken_from_untracked_qty, lot_id=False, package_id=package_id, owner_id=owner_id)
Quant._increase_available_quantity(product_id, location_id, taken_from_untracked_qty, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
Quant._update_available_quantity(product_id, location_id, -taken_from_untracked_qty, lot_id=False, package_id=package_id, owner_id=owner_id)
Quant._update_available_quantity(product_id, location_id, taken_from_untracked_qty, lot_id=lot_id, package_id=package_id, owner_id=owner_id)
ml._free_reservation(ml.product_id, location_id, untracked_qty, lot_id=False, package_id=package_id, owner_id=owner_id)
if location_dest_id.should_impact_quants() and ml.product_id.type == 'product' and qty_done:
Quant._increase_available_quantity(product_id, location_dest_id, quantity, lot_id=lot_id, package_id=result_package_id, owner_id=owner_id)
Quant._update_available_quantity(product_id, location_dest_id, quantity, lot_id=lot_id, package_id=result_package_id, owner_id=owner_id)
# Unreserve and reserve following move in order to have the real reserved quantity on move_line.
next_moves |= ml.move_id.move_dest_ids.filtered(lambda move: move.state not in ('done', 'cancel'))
res = super(StockMoveLine, self).write(vals)
@@ -232,7 +232,7 @@ class StockMoveLine(models.Model):
raise UserError(_('You can not delete pack operations of a done picking'))
# Unlinking a pack operation should unreserve.
if ml.location_id.should_impact_quants() and ml.product_id.type == 'product' and not float_is_zero(ml.product_qty, precision_digits=precision):
self.env['stock.quant']._decrease_reserved_quantity(ml.product_id, ml.location_id, ml.product_qty, lot_id=ml.lot_id,
self.env['stock.quant']._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id,
package_id=ml.package_id, owner_id=ml.owner_id)
return super(StockMoveLine, self).unlink()
@@ -252,23 +252,23 @@ class StockMoveLine(models.Model):
# unreserve what's been reserved
if ml.location_id.should_impact_quants() and ml.product_id.type == 'product' and ml.product_qty:
try:
Quant._decrease_reserved_quantity(ml.product_id, ml.location_id, ml.product_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
except UserError:
Quant._decrease_reserved_quantity(ml.product_id, ml.location_id, ml.product_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id)
Quant._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id)
# move what's been actually done
quantity = ml.product_uom_id._compute_quantity(ml.qty_done, ml.move_id.product_id.uom_id, rounding_method='HALF-UP')
if ml.location_id.should_impact_quants() and ml.product_id.type == 'product':
available_qty = Quant._decrease_available_quantity(ml.product_id, ml.location_id, quantity, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
available_qty = Quant._update_available_quantity(ml.product_id, ml.location_id, -quantity, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
if available_qty < 0 and ml.lot_id:
# see if we can compensate the negative quants with some untracked quants
untracked_qty = Quant._get_available_quantity(ml.product_id, ml.location_id, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
if untracked_qty:
taken_from_untracked_qty = min(untracked_qty, abs(quantity))
Quant._decrease_available_quantity(ml.product_id, ml.location_id, taken_from_untracked_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id)
Quant._increase_available_quantity(ml.product_id, ml.location_id, taken_from_untracked_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
Quant._update_available_quantity(ml.product_id, ml.location_id, -taken_from_untracked_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id)
Quant._update_available_quantity(ml.product_id, ml.location_id, taken_from_untracked_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id)
if ml.location_dest_id.should_impact_quants() and ml.product_id.type == 'product':
Quant._increase_available_quantity(ml.product_id, ml.location_dest_id, quantity, lot_id=ml.lot_id, package_id=ml.result_package_id, owner_id=ml.owner_id)
Quant._update_available_quantity(ml.product_id, ml.location_dest_id, quantity, lot_id=ml.lot_id, package_id=ml.result_package_id, owner_id=ml.owner_id)
def _free_reservation(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None):
""" When editing a done move line or validating one with some forced quantities, it is
+1 -3
View File
@@ -778,8 +778,6 @@ class Picking(models.Model):
@api.multi
def button_scrap(self):
self.ensure_one()
# only stockable products are scrapeable
scrapeable_products = self.move_line_ids.mapped('product_id').filtered(lambda p: p.type == 'product')
return {
'name': _('Scrap'),
'view_type': 'form',
@@ -787,7 +785,7 @@ class Picking(models.Model):
'res_model': 'stock.scrap',
'view_id': self.env.ref('stock.stock_scrap_form_view2').id,
'type': 'ir.actions.act_window',
'context': {'default_picking_id': self.id, 'product_ids': scrapeable_products.ids},
'context': {'default_picking_id': self.id, 'product_ids': self.move_line_ids.mapped('product_id').ids},
'target': 'new',
}
+10 -35
View File
@@ -19,11 +19,8 @@ class StockQuant(models.Model):
product_uom_id = fields.Many2one(
'product.uom', 'Unit of Measure',
readonly=True, related='product_id.uom_id')
company_id = fields.Many2one(
'res.company', 'Company',
default=lambda self: self.env['res.company']._company_default_get('stock.quant'),
help='The company to which the quants belong',
readonly=True, required=True)
company_id = fields.Many2one(related='location_id.company_id',
string='Company', store=True, readonly=True)
location_id = fields.Many2one(
'stock.location', 'Location',
auto_join=True, ondelete='restrict', readonly=True, required=True)
@@ -50,8 +47,8 @@ class StockQuant(models.Model):
@api.multi
@api.constrains('product_id')
def check_product_id(self):
if any(elem.product_id.type == 'consu' for elem in self):
raise ValidationError(_('Quants cannot be created for consumables.'))
if any(elem.product_id.type != 'product' for elem in self):
raise ValidationError(_('Quants cannot be created for consumables or services.'))
@api.multi
@api.constrains('quantity')
@@ -88,7 +85,6 @@ class StockQuant(models.Model):
removal_strategy_order = self._get_removal_strategy_order(removal_strategy)
domain = [
('product_id', '=', product_id.id),
('location_id', 'child_of', location_id.id),
]
if not strict:
if lot_id:
@@ -97,19 +93,15 @@ class StockQuant(models.Model):
domain = expression.AND([[('package_id', '=', package_id.id)], domain])
if owner_id:
domain = expression.AND([[('owner_id', '=', owner_id.id)], domain])
domain = expression.AND([[('location_id', 'child_of', location_id.id)], domain])
else:
domain = expression.AND([[('lot_id', '=', lot_id and lot_id.id or False)], domain])
domain = expression.AND([[('package_id', '=', package_id and package_id.id or False)], domain])
domain = expression.AND([[('owner_id', '=', owner_id and owner_id.id or False)], domain])
domain = expression.AND([[('location_id', '=', location_id.id)], domain])
return self.search(domain, order=removal_strategy_order)
@api.model
def _get_quantity(self, product_id, location_id, lot_id=None, package_id=None, owner_id=None, strict=False):
self = self.sudo()
quants = self._gather(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=strict)
return sum(quants.mapped('quantity'))
@api.model
def _get_available_quantity(self, product_id, location_id, lot_id=None, package_id=None, owner_id=None, strict=False):
""" Return the available quantity, i.e. the sum of `quantity` minus the sum of
@@ -134,9 +126,9 @@ class StockQuant(models.Model):
return sum(quants.mapped('quantity')) - sum(quants.mapped('reserved_quantity'))
@api.model
def _increase_available_quantity(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, strict=True):
def _update_available_quantity(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None):
self = self.sudo()
quants = self._gather(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=strict)
quants = self._gather(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=True)
for quant in quants:
try:
with self._cr.savepoint():
@@ -160,14 +152,10 @@ class StockQuant(models.Model):
'package_id': package_id and package_id.id,
'owner_id': owner_id and owner_id.id,
})
return self._get_available_quantity(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=strict)
return self._get_available_quantity(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=False)
@api.model
def _decrease_available_quantity(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, strict=True):
return self._increase_available_quantity(product_id, location_id, -quantity, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=strict)
@api.model
def _increase_reserved_quantity(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, strict=False):
def _update_reserved_quantity(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, strict=False):
""" Increase the reserved quantity, i.e. increase `reserved_quantity` for the set of quants
sharing the combination of `product_id, location_id` if `strict` is set to False or sharing
the *exact same characteristics* otherwise. Typically, this method is called when reserving
@@ -209,19 +197,6 @@ class StockQuant(models.Model):
break
return reserved_quants
@api.model
def _decrease_reserved_quantity(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, strict=True):
""" Decrease the reserved quantity, i.e. decrease `reserved_quantity`, for the set of
quants sharing the *exact same characteristics* if `strict` is set to True or sharing the
combination of `product_id, location_id` otherwise. Typically, this method is called during
a move line's validation or a move line's unlink and `strict` should be `True` in these
cases, because the characteristics are known.
:return: a list of tuples (quant, quantity_unreserved) showing on which quant the decrease
of reservation was done and how much the system was able to unreserve on it
"""
return self._increase_reserved_quantity(product_id, location_id, -quantity, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=strict)
class QuantPackage(models.Model):
""" Packages containing quants and/or other packages """
+10 -8
View File
@@ -79,15 +79,18 @@ class StockScrap(models.Model):
def do_scrap(self):
for scrap in self:
move = self.env['stock.move'].create(scrap._prepare_move_values())
quantity_in_stock = self.env['stock.quant']._get_quantity(
self.product_id, self.location_id, lot_id=self.lot_id,
package_id=self.package_id, owner_id=self.owner_id, strict=True
)
if quantity_in_stock < move.product_qty: # FIXME: float compare
raise UserError(_('You cannot scrap a move without having available stock for %s. You can correct it with an inventory adjustment.') % move.product_id.name)
if self.product_id.type == 'product':
quantity_in_stock = sum(self.env['stock.quant'].search([
('product_id', '=', self.product_id.id),
('lot_id', '=', self.lot_id and self.lot_id.id or False),
('package_id', '=', self.package_id and self.package_id.id or False),
('owner_id', '=', self.owner_id and self.owner_id.id or False),
('location_id', 'child_of', self.location_id.id)
]).mapped('quantity'))
if quantity_in_stock < move.product_qty: # FIXME: float compare
raise UserError(_('You cannot scrap a move without having available stock for %s. You can correct it with an inventory adjustment.') % move.product_id.name)
move.action_done()
scrap.write({'move_id': move.id, 'state': 'done'})
# TODO: unreserve reserved
return True
def _prepare_move_values(self):
@@ -109,7 +112,6 @@ class StockScrap(models.Model):
'package_id': self.package_id.id,
'owner_id': self.owner_id.id,
'lot_id': self.lot_id.id, })],
# 'restrict_lot_id': self.lot_id.id, # FIXME: JCO i asked you x times, should we drop this or not????
# 'restrict_partner_id': self.owner_id.id,
'picking_id': self.picking_id.id
}
+25 -1
View File
@@ -26,7 +26,7 @@ class TestInventory(TransactionCase):
as expected.
"""
# make some stock
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 100)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 100)
self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 100.0)
@@ -141,3 +141,27 @@ class TestInventory(TransactionCase):
self.assertEqual(len(self.env['stock.quant']._gather(self.product2, self.stock_location, lot_id=lot1, strict=True)), 1.0)
self.assertEqual(len(self.env['stock.quant']._gather(self.product2, self.stock_location, strict=True)), 1.0)
self.assertEqual(len(self.env['stock.quant']._gather(self.product2, self.stock_location)), 2.0)
def test_inventory_5(self):
""" Check that assigning an owner does work.
"""
owner1 = self.env['res.partner'].create({'name': 'test_inventory_5'})
inventory = self.env['stock.inventory'].create({
'name': 'remove product1',
'filter': 'product',
'location_id': self.stock_location.id,
'product_id': self.product1.id,
'exhausted': True,
})
inventory.prepare_inventory()
self.assertEqual(len(inventory.line_ids), 1)
self.assertEqual(inventory.line_ids.theoretical_qty, 0)
inventory.line_ids.partner_id = owner1
inventory.line_ids.product_qty = 5
inventory.action_done()
quant = self.env['stock.quant']._gather(self.product1, self.stock_location)
self.assertEqual(len(quant), 1)
self.assertEqual(quant.quantity, 5)
self.assertEqual(quant.owner_id.id, owner1.id)
+142 -59
View File
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests.common import TransactionCase
from odoo.exceptions import UserError
from odoo.tests.common import TransactionCase
class StockMove(TransactionCase):
@@ -30,6 +30,11 @@ class StockMove(TransactionCase):
'tracking': 'lot',
'categ_id': self.env.ref('product.product_category_all').id,
})
self.product4 = self.env['product.product'].create({
'name': 'Product A',
'type': 'consu',
'categ_id': self.env.ref('product.product_category_all').id,
})
def test_in_1(self):
""" Receive products from a supplier. Check that a move line is created and that the
@@ -164,7 +169,7 @@ class StockMove(TransactionCase):
stock and that the delivery correctly remove the single quant in stock.
"""
# make some stock
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 100)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 100)
self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 100.0)
@@ -265,8 +270,8 @@ class StockMove(TransactionCase):
'name': 'lot1',
'product_id': self.product3.id,
})
self.env['stock.quant']._increase_available_quantity(self.product3, self.stock_location, 2)
self.env['stock.quant']._increase_available_quantity(self.product3, self.stock_location, 3, lot_id=lot1)
self.env['stock.quant']._update_available_quantity(self.product3, self.stock_location, 2)
self.env['stock.quant']._update_available_quantity(self.product3, self.stock_location, 3, lot_id=lot1)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product3, self.stock_location), 5.0)
# creation
@@ -297,9 +302,9 @@ class StockMove(TransactionCase):
'name': 'lot2',
'product_id': self.product2.id,
})
self.env['stock.quant']._increase_available_quantity(self.product2, self.stock_location, 2)
self.env['stock.quant']._increase_available_quantity(self.product2, self.stock_location, 1, lot_id=lot1)
self.env['stock.quant']._increase_available_quantity(self.product2, self.stock_location, 1, lot_id=lot2)
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 2)
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 1, lot_id=lot1)
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 1, lot_id=lot2)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location), 4.0)
# creation
@@ -363,8 +368,8 @@ class StockMove(TransactionCase):
'name': 'lot2',
'product_id': self.product2.id,
})
self.env['stock.quant']._increase_available_quantity(self.product2, self.stock_location, 1, lot_id=lot1)
self.env['stock.quant']._increase_available_quantity(self.product2, self.stock_location, 1, lot_id=lot2)
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 1, lot_id=lot1)
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 1, lot_id=lot2)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location), 2.0)
# creation
@@ -381,7 +386,7 @@ class StockMove(TransactionCase):
move1.move_line_ids.write({'qty_done': 1.0})
move1.action_done()
self.env['stock.quant']._increase_available_quantity(self.product2, self.stock_location, 2)
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 2)
lot3 = self.env['stock.production.lot'].create({
'name': 'lot3',
'product_id': self.product2.id,
@@ -430,8 +435,8 @@ class StockMove(TransactionCase):
'name': 'lot2',
'product_id': self.product2.id,
})
self.env['stock.quant']._increase_available_quantity(self.product2, self.stock_location, 1, lot_id=lot1)
self.env['stock.quant']._increase_available_quantity(self.product2, self.stock_location, 1, lot_id=lot2)
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 1, lot_id=lot1)
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 1, lot_id=lot2)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location), 2.0)
# creation
@@ -452,7 +457,7 @@ class StockMove(TransactionCase):
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location, lot_id=lot1, strict=True), 0.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location, lot_id=lot2, strict=True), 0.0)
self.env['stock.quant']._increase_available_quantity(self.product2, self.stock_location, 1)
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 1)
lot3 = self.env['stock.production.lot'].create({
'name': 'lot3',
'product_id': self.product2.id,
@@ -479,7 +484,7 @@ class StockMove(TransactionCase):
self.assertEqual(move1.state, 'confirmed')
# create an untracked quant
self.env['stock.quant']._increase_available_quantity(self.product2, self.stock_location, 1.0)
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 1.0)
lot1 = self.env['stock.production.lot'].create({
'name': 'lot1',
'product_id': self.product2.id,
@@ -505,7 +510,7 @@ class StockMove(TransactionCase):
def test_mixed_tracking_reservation_6(self):
# create an untracked quant
self.env['stock.quant']._increase_available_quantity(self.product2, self.stock_location, 1.0)
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 1.0)
move1 = self.env['stock.move'].create({
'name': 'test_jenaimarre_1',
'location_id': self.stock_location.id,
@@ -586,7 +591,7 @@ class StockMove(TransactionCase):
more than enough products in stock.
"""
# make some stock
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 150.0)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 150.0)
self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0)
move1 = self.env['stock.move'].create({
@@ -607,7 +612,7 @@ class StockMove(TransactionCase):
not enough products in stock.
"""
# make some stock
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 50.0)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 50.0)
self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0)
move1 = self.env['stock.move'].create({
@@ -628,7 +633,7 @@ class StockMove(TransactionCase):
set the state back to confirmed.
"""
# make some stock
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 150.0)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 150.0)
# creation
move1 = self.env['stock.move'].create({
@@ -666,7 +671,7 @@ class StockMove(TransactionCase):
package1 = self.env['stock.quant.package'].create({'name': 'test_unreserve_2_pack'})
# make some stock
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 150.0, package_id=package1)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 150.0, package_id=package1)
# creation
move1 = self.env['stock.move'].create({
@@ -701,7 +706,7 @@ class StockMove(TransactionCase):
""" Similar to `test_unreserve_1` but checking the quants more in details.
"""
# make some stock
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 2)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2)
self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 2)
@@ -742,7 +747,7 @@ class StockMove(TransactionCase):
""" Check the unreservation of a partially available stock move.
"""
# make some stock
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 2)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2)
self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 2)
@@ -784,7 +789,7 @@ class StockMove(TransactionCase):
untracked product.
"""
# make some stock
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0)
self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0)
move_stock_pack = self.env['stock.move'].create({
@@ -826,7 +831,7 @@ class StockMove(TransactionCase):
})
# make some stock
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot1)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot1)
self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location, lot1)), 1.0)
move_stock_pack = self.env['stock.move'].create({
@@ -872,7 +877,7 @@ class StockMove(TransactionCase):
move multiple units of an untracked product.
"""
# make some stock
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 2.0)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2.0)
self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1.0)
move_stock_pack_1 = self.env['stock.move'].create({
@@ -948,7 +953,7 @@ class StockMove(TransactionCase):
})
# make some stock
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 2.0, lot_id=lot1)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2.0, lot_id=lot1)
self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location, lot1)), 1.0)
move_stock_pack_1 = self.env['stock.move'].create({
@@ -1010,7 +1015,7 @@ class StockMove(TransactionCase):
move multiple units of an untracked product.
"""
# make some stock
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 2.0)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 2.0)
move_stock_pack = self.env['stock.move'].create({
'name': 'test_link_assign_1_1',
@@ -1118,7 +1123,7 @@ class StockMove(TransactionCase):
correctly unreserves the other one.
"""
# make some stock
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0)
# prepare the conflicting move
move1 = self.env['stock.move'].create({
@@ -1179,7 +1184,7 @@ class StockMove(TransactionCase):
})
# make some stock
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot1)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot1)
# prepare the conflicting move
move1 = self.env['stock.move'].create({
@@ -1245,8 +1250,8 @@ class StockMove(TransactionCase):
'usage': 'internal',
'location_id': self.stock_location.id,
})
self.env['stock.quant']._increase_available_quantity(self.product1, shelf1_location, 1.0)
self.env['stock.quant']._increase_available_quantity(self.product1, shelf2_location, 1.0)
self.env['stock.quant']._update_available_quantity(self.product1, shelf1_location, 1.0)
self.env['stock.quant']._update_available_quantity(self.product1, shelf2_location, 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, shelf1_location), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, shelf2_location), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 2.0)
@@ -1285,8 +1290,8 @@ class StockMove(TransactionCase):
'name': 'lot2',
'product_id': self.product1.id,
})
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot1)
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot2)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot1)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot2)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, lot_id=lot1), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, lot_id=lot2), 1.0)
@@ -1321,8 +1326,8 @@ class StockMove(TransactionCase):
package1 = self.env['stock.quant.package'].create({'name': 'test_edit_reserved_move_line_3'})
package2 = self.env['stock.quant.package'].create({'name': 'test_edit_reserved_move_line_3'})
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0, package_id=package1)
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0, package_id=package2)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, package_id=package1)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, package_id=package2)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, package_id=package1), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, package_id=package2), 1.0)
@@ -1356,8 +1361,8 @@ class StockMove(TransactionCase):
owner1 = self.env['res.partner'].create({'name': 'test_edit_reserved_move_line_4_1'})
owner2 = self.env['res.partner'].create({'name': 'test_edit_reserved_move_line_4_2'})
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0, owner_id=owner1)
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0, owner_id=owner2)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, owner_id=owner1)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, owner_id=owner2)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, owner_id=owner1), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, owner_id=owner2), 1.0)
@@ -1399,8 +1404,8 @@ class StockMove(TransactionCase):
})
package1 = self.env['stock.quant.package'].create({'name': 'test_edit_reserved_move_line_5'})
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot1, package_id=package1)
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot2)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot1, package_id=package1)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot2)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, lot_id=lot1, package_id=package1), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, lot_id=lot2), 1.0)
@@ -1442,7 +1447,7 @@ class StockMove(TransactionCase):
'usage': 'internal',
'location_id': self.stock_location.id,
})
self.env['stock.quant']._increase_available_quantity(self.product1, shelf1_location, 1.0)
self.env['stock.quant']._update_available_quantity(self.product1, shelf1_location, 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, shelf1_location), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, shelf2_location), 0.0)
@@ -1481,7 +1486,7 @@ class StockMove(TransactionCase):
'product_id': self.product3.id,
})
# make some stock without assigning a lot id
self.env['stock.quant']._increase_available_quantity(self.product3, self.stock_location, 5)
self.env['stock.quant']._update_available_quantity(self.product3, self.stock_location, 5)
# creation
move1 = self.env['stock.move'].create({
@@ -1533,8 +1538,8 @@ class StockMove(TransactionCase):
'product_id': self.product3.id,
})
# make some stock without assigning a lot id
self.env['stock.quant']._increase_available_quantity(self.product3, self.stock_location, 3)
self.env['stock.quant']._increase_available_quantity(self.product3, self.stock_location, 2, lot_id=lot1)
self.env['stock.quant']._update_available_quantity(self.product3, self.stock_location, 3)
self.env['stock.quant']._update_available_quantity(self.product3, self.stock_location, 2, lot_id=lot1)
# creation
move1 = self.env['stock.move'].create({
@@ -1600,8 +1605,8 @@ class StockMove(TransactionCase):
'usage': 'internal',
'location_id': self.stock_location.id,
})
self.env['stock.quant']._increase_available_quantity(self.product1, shelf1_location, 1.0)
self.env['stock.quant']._increase_available_quantity(self.product1, shelf2_location, 1.0)
self.env['stock.quant']._update_available_quantity(self.product1, shelf1_location, 1.0)
self.env['stock.quant']._update_available_quantity(self.product1, shelf2_location, 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, shelf1_location), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, shelf2_location), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 2.0)
@@ -1643,8 +1648,8 @@ class StockMove(TransactionCase):
'name': 'lot2',
'product_id': self.product1.id,
})
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot1)
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot2)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot1)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot2)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, lot_id=lot1), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, lot_id=lot2), 1.0)
@@ -1680,8 +1685,8 @@ class StockMove(TransactionCase):
package1 = self.env['stock.quant.package'].create({'name': 'test_edit_reserved_move_line_3'})
package2 = self.env['stock.quant.package'].create({'name': 'test_edit_reserved_move_line_3'})
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0, package_id=package1)
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0, package_id=package2)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, package_id=package1)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, package_id=package2)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, package_id=package1), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, package_id=package2), 1.0)
@@ -1716,8 +1721,8 @@ class StockMove(TransactionCase):
owner1 = self.env['res.partner'].create({'name': 'test_edit_reserved_move_line_4_1'})
owner2 = self.env['res.partner'].create({'name': 'test_edit_reserved_move_line_4_2'})
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0, owner_id=owner1)
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0, owner_id=owner2)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, owner_id=owner1)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, owner_id=owner2)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, owner_id=owner1), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, owner_id=owner2), 1.0)
@@ -1760,8 +1765,8 @@ class StockMove(TransactionCase):
})
package1 = self.env['stock.quant.package'].create({'name': 'test_edit_reserved_move_line_5'})
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot1, package_id=package1)
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot2)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot1, package_id=package1)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1.0, lot_id=lot2)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, lot_id=lot1, package_id=package1), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location, lot_id=lot2), 1.0)
@@ -1804,7 +1809,7 @@ class StockMove(TransactionCase):
'usage': 'internal',
'location_id': self.stock_location.id,
})
self.env['stock.quant']._increase_available_quantity(self.product1, shelf1_location, 1.0)
self.env['stock.quant']._update_available_quantity(self.product1, shelf1_location, 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, shelf1_location), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, shelf2_location), 0.0)
@@ -1848,8 +1853,8 @@ class StockMove(TransactionCase):
'usage': 'internal',
'location_id': self.stock_location.id,
})
self.env['stock.quant']._increase_available_quantity(self.product1, shelf1_location, 1.0)
self.env['stock.quant']._increase_available_quantity(self.product1, shelf2_location, 1.0)
self.env['stock.quant']._update_available_quantity(self.product1, shelf1_location, 1.0)
self.env['stock.quant']._update_available_quantity(self.product1, shelf2_location, 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, shelf1_location), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, shelf2_location), 1.0)
@@ -1900,7 +1905,7 @@ class StockMove(TransactionCase):
'usage': 'internal',
'location_id': self.stock_location.id,
})
self.env['stock.quant']._increase_available_quantity(self.product1, shelf1_location, 1.0)
self.env['stock.quant']._update_available_quantity(self.product1, shelf1_location, 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, shelf1_location), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 1.0)
@@ -1936,7 +1941,7 @@ class StockMove(TransactionCase):
'usage': 'internal',
'location_id': self.stock_location.id,
})
self.env['stock.quant']._increase_available_quantity(self.product1, shelf1_location, 1.0)
self.env['stock.quant']._update_available_quantity(self.product1, shelf1_location, 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, shelf1_location), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 1.0)
@@ -2065,7 +2070,7 @@ class StockMove(TransactionCase):
remaining quantity.
"""
owner = self.env['res.partner'].create({'name': 'Jean'})
self.env['stock.quant']._increase_available_quantity(self.product1, self.stock_location, 5.0)
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 5.0)
picking = self.env['stock.picking'].create({
'location_id': self.stock_location.id,
'location_dest_id': self.customer_location.id,
@@ -2146,7 +2151,7 @@ class StockMove(TransactionCase):
'name': 'lot1',
'product_id': self.product3.id,
})
self.env['stock.quant']._increase_available_quantity(self.product3, self.stock_location, 5.0, lot_id=lot1)
self.env['stock.quant']._update_available_quantity(self.product3, self.stock_location, 5.0, lot_id=lot1)
picking = self.env['stock.picking'].create({
'location_id': self.stock_location.id,
'location_dest_id': self.customer_location.id,
@@ -2202,3 +2207,81 @@ class StockMove(TransactionCase):
(move1 + move2).write({'quantity_done': 1})
self.assertEqual(move1.quantity_done, 1)
self.assertEqual(move2.quantity_done, 1)
def test_initial_demand_1(self):
""" Check that the initial demand is set to 0 when creating a move by hand, and
that changing the product on the move do not reset the initial demand.
"""
move1 = self.env['stock.move'].create({
'name': 'test_in_1',
'location_id': self.supplier_location.id,
'location_dest_id': self.stock_location.id,
'product_id': self.product1.id,
'product_uom': self.uom_unit.id,
})
self.assertEqual(move1.state, 'draft')
self.assertEqual(move1.product_uom_qty, 0)
move1.product_uom_qty = 100
move1.product_id = self.product2
move1.onchange_product_id()
self.assertEqual(move1.product_uom_qty, 100)
def test_scrap_1(self):
""" Check the created stock move and the impact on quants when we scrap a
stockable product.
"""
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1)
scrap = self.env['stock.scrap'].create({
'product_id': self.product1.id,
'product_uom_id':self.product1.uom_id.id,
'scrap_qty': 1,
})
self.assertEqual(scrap.state, 'done')
move = scrap.move_id
self.assertEqual(move.state, 'done')
self.assertEqual(move.quantity_done, 1)
self.assertEqual(move.scrapped, True)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product1, self.stock_location), 0)
def test_scrap_2(self):
""" Check the created stock move and the impact on quants when we scrap a
consumable product.
"""
scrap = self.env['stock.scrap'].create({
'product_id': self.product4.id,
'product_uom_id':self.product4.uom_id.id,
'scrap_qty': 1,
})
self.assertEqual(scrap.state, 'done')
move = scrap.move_id
self.assertEqual(move.state, 'done')
self.assertEqual(move.quantity_done, 1)
self.assertEqual(move.scrapped, True)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product4, self.stock_location), 0)
def test_scrap_3(self):
""" Scrap the product of a reserved move line. Check that the move line is
correctly deleted and that the associated stock move is not assigned anymore.
"""
self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 1)
move1 = self.env['stock.move'].create({
'name': 'test_set_quantity_done_1',
'location_id': self.stock_location.id,
'location_dest_id': self.customer_location.id,
'product_id': self.product1.id,
'product_uom': self.uom_unit.id,
'product_uom_qty': 1.0,
})
move1.action_confirm()
move1.action_assign()
self.assertEqual(move1.state, 'assigned')
self.assertEqual(len(move1.move_line_ids), 1)
scrap = self.env['stock.scrap'].create({
'product_id': self.product1.id,
'product_uom_id':self.product1.uom_id.id,
'scrap_qty': 1,
})
self.assertEqual(move1.state, 'confirmed')
self.assertEqual(len(move1.move_line_ids), 0)
+15 -15
View File
@@ -52,7 +52,7 @@ class TestPickShip(TestStockCommon):
location = self.env['stock.location'].browse(self.stock_location)
# make some stock
self.env['stock.quant']._increase_available_quantity(self.productA, location, 10.0)
self.env['stock.quant']._update_available_quantity(self.productA, location, 10.0)
picking_pick.action_assign()
picking_pick.move_lines[0].move_line_ids[0].qty_done = 10.0
picking_pick.do_transfer()
@@ -72,9 +72,9 @@ class TestPickShip(TestStockCommon):
"""
picking_pick, picking_client = self.create_pick_ship()
stock_location = self.env['stock.location'].browse(self.stock_location)
self.env['stock.quant']._increase_available_quantity(self.productA, stock_location, 10.0)
self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 10.0)
pack_location = self.env['stock.location'].browse(self.pack_location)
self.env['stock.quant']._increase_available_quantity(self.productA, pack_location, 5.0)
self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 5.0)
self.assertEqual(len(self.env['stock.quant']._gather(self.productA, stock_location)), 1.0)
self.assertEqual(len(self.env['stock.quant']._gather(self.productA, pack_location)), 1.0)
@@ -105,7 +105,7 @@ class TestPickShip(TestStockCommon):
def test_mto_moves_return(self):
picking_pick, picking_client = self.create_pick_ship()
stock_location = self.env['stock.location'].browse(self.stock_location)
self.env['stock.quant']._increase_available_quantity(self.productA, stock_location, 10.0)
self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 10.0)
picking_pick.action_assign()
picking_pick.move_lines[0].move_line_ids[0].qty_done = 10.0
@@ -135,7 +135,7 @@ class TestPickShip(TestStockCommon):
location = self.env['stock.location'].browse(self.stock_location)
# make some stock
self.env['stock.quant']._increase_available_quantity(self.productA, location, 10.0)
self.env['stock.quant']._update_available_quantity(self.productA, location, 10.0)
picking_pick.action_assign()
picking_pick.move_lines[0].move_line_ids[0].qty_done = 5.0
@@ -159,7 +159,7 @@ class TestPickShip(TestStockCommon):
location = self.env['stock.location'].browse(self.stock_location)
# make some stock
self.env['stock.quant']._increase_available_quantity(self.productA, location, 10.0)
self.env['stock.quant']._update_available_quantity(self.productA, location, 10.0)
picking_pick.action_assign()
picking_pick.move_lines[0].move_line_ids[0].qty_done = 10.0
picking_pick.action_done()
@@ -197,7 +197,7 @@ class TestPickShip(TestStockCommon):
location = self.env['stock.location'].browse(self.stock_location)
# make some stock
self.env['stock.quant']._increase_available_quantity(self.productA, location, 10.0)
self.env['stock.quant']._update_available_quantity(self.productA, location, 10.0)
picking_pick.action_assign()
picking_pick.move_lines[0].move_line_ids[0].write({
'qty_done': 10.0,
@@ -246,7 +246,7 @@ class TestSinglePicking(TestStockCommon):
# make some stock
pack_location = self.env['stock.location'].browse(self.pack_location)
self.env['stock.quant']._increase_available_quantity(self.productA, pack_location, 2)
self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 2)
# assign
delivery_order.action_confirm()
@@ -283,7 +283,7 @@ class TestSinglePicking(TestStockCommon):
# make some stock
pack_location = self.env['stock.location'].browse(self.pack_location)
self.env['stock.quant']._increase_available_quantity(self.productA, pack_location, 1)
self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 1)
# assign to partially available
delivery_order.action_confirm()
@@ -322,7 +322,7 @@ class TestSinglePicking(TestStockCommon):
# make some stock
pack_location = self.env['stock.location'].browse(self.pack_location)
self.env['stock.quant']._increase_available_quantity(self.productA, pack_location, 1)
self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 1)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 1.0)
# assign to available
@@ -377,7 +377,7 @@ class TestSinglePicking(TestStockCommon):
# make some stock
pack_location = self.env['stock.location'].browse(self.pack_location)
self.env['stock.quant']._increase_available_quantity(self.productA, pack_location, 1)
self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 1)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 1.0)
# assign to available
@@ -463,7 +463,7 @@ class TestSinglePicking(TestStockCommon):
product with one move line. After adding a second unit in stock and recheck availability.
The DO should have 2 reserved unit, be in available state and have only one move line.
"""
self.env['stock.quant']._increase_available_quantity(self.productA, self.env['stock.location'].browse(self.stock_location), 1.0)
self.env['stock.quant']._update_available_quantity(self.productA, self.env['stock.location'].browse(self.stock_location), 1.0)
delivery_order = self.env['stock.picking'].create({
'location_id': self.stock_location,
'location_dest_id': self.customer_location,
@@ -520,7 +520,7 @@ class TestSinglePicking(TestStockCommon):
'product_id': self.productA.id,
})
stock_location = self.env['stock.location'].browse(self.stock_location)
self.env['stock.quant']._increase_available_quantity(self.productA, stock_location, 1.0, lot_id=lot1)
self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 1.0, lot_id=lot1)
delivery_order = self.env['stock.picking'].create({
'location_id': self.stock_location,
'location_dest_id': self.customer_location,
@@ -580,7 +580,7 @@ class TestSinglePicking(TestStockCommon):
'product_id': self.productA.id,
})
stock_location = self.env['stock.location'].browse(self.stock_location)
self.env['stock.quant']._increase_available_quantity(self.productA, stock_location, 1.0, lot_id=lot1)
self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 1.0, lot_id=lot1)
delivery_order = self.env['stock.picking'].create({
'location_id': self.stock_location,
'location_dest_id': self.customer_location,
@@ -647,7 +647,7 @@ class TestSinglePicking(TestStockCommon):
'product_id': self.productA.id,
})
stock_location = self.env['stock.location'].browse(self.stock_location)
self.env['stock.quant']._increase_available_quantity(self.productA, stock_location, 1.0, lot_id=serial1)
self.env['stock.quant']._update_available_quantity(self.productA, stock_location, 1.0, lot_id=serial1)
delivery_order = self.env['stock.picking'].create({
'location_id': self.stock_location,
'location_dest_id': self.customer_location,
+53 -27
View File
@@ -164,7 +164,7 @@ class StockQuant(TransactionCase):
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 0.0)
self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 0)
with self.assertRaises(ValidationError):
self.env['stock.quant']._increase_available_quantity(product1, stock_location, 1.0)
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0)
def test_get_available_quantity_9(self):
""" Quantity availability by a demo user with access rights/rules.
@@ -190,7 +190,7 @@ class StockQuant(TransactionCase):
'name': 'Product A',
'type': 'product',
})
self.env['stock.quant']._increase_available_quantity(product1, stock_location, 1.0)
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 1.0)
def test_increase_available_quantity_2(self):
@@ -208,7 +208,7 @@ class StockQuant(TransactionCase):
'quantity': 1.0,
})
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 2.0)
self.env['stock.quant']._increase_available_quantity(product1, stock_location, 1.0)
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 3.0)
self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 2)
@@ -228,7 +228,7 @@ class StockQuant(TransactionCase):
quant_id = cr2.fetchone()
cr2.execute("SELECT 1 FROM stock_quant WHERE id=%s FOR UPDATE", quant_id)
self.env['stock.quant']._increase_available_quantity(product, stock_location, 1.0)
self.env['stock.quant']._update_available_quantity(product, stock_location, 1.0)
cr2.rollback()
cr2.close()
self.assertEqual(self.env['stock.quant']._get_available_quantity(product, stock_location), 11.0)
@@ -243,7 +243,33 @@ class StockQuant(TransactionCase):
'type': 'product',
})
self.env = self.env(user=self.env.ref('base.user_demo'))
self.env['stock.quant']._increase_available_quantity(product1, stock_location, 1.0)
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0)
def test_increase_available_quantity_5(self):
""" Increase the available quantity when no quants are already in stock.
Increase a subLocation and check that quants are in this location. Also test inverse.
"""
stock_location = self.env.ref('stock.stock_location_stock')
stock_sub_location = stock_location.child_ids[0]
product1 = self.env['product.product'].create({
'name': 'Product A',
'type': 'product',
})
product2 = self.env['product.product'].create({
'name': 'Product B',
'type': 'product',
})
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0)
self.env['stock.quant']._update_available_quantity(product1, stock_sub_location, 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_sub_location), 1.0)
self.env['stock.quant']._update_available_quantity(product2, stock_sub_location, 1.0)
self.env['stock.quant']._update_available_quantity(product2, stock_location, 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product2, stock_location), 2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product2, stock_sub_location), 1.0)
def test_decrease_available_quantity_1(self):
""" Decrease the available quantity when no quants are already in a location.
@@ -253,7 +279,7 @@ class StockQuant(TransactionCase):
'name': 'Product A',
'type': 'product',
})
self.env['stock.quant']._decrease_available_quantity(product1, stock_location, 1.0)
self.env['stock.quant']._update_available_quantity(product1, stock_location, -1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), -1.0)
def test_decrease_available_quantity_2(self):
@@ -272,7 +298,7 @@ class StockQuant(TransactionCase):
})
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 2.0)
self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 2)
self.env['stock.quant']._decrease_available_quantity(product1, stock_location, 1.0)
self.env['stock.quant']._update_available_quantity(product1, stock_location, -1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 1.0)
self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 1)
@@ -290,7 +316,7 @@ class StockQuant(TransactionCase):
# quantity increase
cr2 = self.registry.cursor()
cr2.execute("SELECT 1 FROM stock_quant WHERE id = %s FOR UPDATE", quants.ids)
self.env['stock.quant']._decrease_available_quantity(product, stock_location, 1.0)
self.env['stock.quant']._update_available_quantity(product, stock_location, -1.0)
cr2.rollback()
cr2.close()
self.assertEqual(self.env['stock.quant']._get_available_quantity(product, stock_location), 9.0)
@@ -311,7 +337,7 @@ class StockQuant(TransactionCase):
'quantity': 1.0,
})
self.env = self.env(user=self.demo_user)
self.env['stock.quant']._decrease_available_quantity(product1, stock_location, 1.0)
self.env['stock.quant']._update_available_quantity(product1, stock_location, -1.0)
self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 0)
def test_increase_reserved_quantity_1(self):
@@ -330,7 +356,7 @@ class StockQuant(TransactionCase):
})
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 10.0)
self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 1)
self.env['stock.quant']._increase_reserved_quantity(product1, stock_location, 10.0)
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 10.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 0.0)
self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 1)
@@ -351,7 +377,7 @@ class StockQuant(TransactionCase):
})
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 10.0)
self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 2)
self.env['stock.quant']._increase_reserved_quantity(product1, stock_location, 10.0)
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 10.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 0.0)
self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 2)
@@ -392,7 +418,7 @@ class StockQuant(TransactionCase):
# total reserved quantity: 29
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 29.0)
self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 4)
self.env['stock.quant']._increase_reserved_quantity(product1, stock_location, 10.0)
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 10.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 19.0)
self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 4)
@@ -420,7 +446,7 @@ class StockQuant(TransactionCase):
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 0.0)
self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 2)
with self.assertRaises(UserError):
self.env['stock.quant']._increase_reserved_quantity(product1, stock_location, 10.0)
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 10.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 0.0)
def test_increase_reserved_quantity_5(self):
@@ -432,7 +458,7 @@ class StockQuant(TransactionCase):
'type': 'product',
})
with self.assertRaises(UserError):
self.env['stock.quant']._increase_reserved_quantity(product1, stock_location, 1.0)
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 0.0)
def test_decrease_reserved_quantity_1(self):
@@ -449,7 +475,7 @@ class StockQuant(TransactionCase):
})
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 0.0)
self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 1)
self.env['stock.quant']._decrease_reserved_quantity(product1, stock_location, 10.0)
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, -10.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 10.0)
self.assertEqual(len(self.env['stock.quant']._gather(product1, stock_location)), 1)
@@ -462,10 +488,10 @@ class StockQuant(TransactionCase):
'type': 'product',
})
with self.assertRaises(UserError):
self.env['stock.quant']._increase_reserved_quantity(product1, stock_location, 1.0)
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 0.0)
with self.assertRaises(UserError):
self.env['stock.quant']._decrease_reserved_quantity(product1, stock_location, 1.0)
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, -1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 0.0)
def test_action_done_1(self):
@@ -475,15 +501,15 @@ class StockQuant(TransactionCase):
'name': 'Product A',
'type': 'product',
})
self.env['stock.quant']._increase_available_quantity(product1, stock_location, 2.0)
self.env['stock.quant']._update_available_quantity(product1, stock_location, 2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 2.0)
self.env['stock.quant']._increase_reserved_quantity(product1, stock_location, 2.0)
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 0.0)
self.env['stock.quant']._decrease_reserved_quantity(product1, stock_location, 2.0)
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, -2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 2.0)
self.env['stock.quant']._decrease_available_quantity(product1, stock_location, 2.0)
self.env['stock.quant']._update_available_quantity(product1, stock_location, -2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 0.0)
self.env['stock.quant']._increase_available_quantity(product1, pack_location, 2.0)
self.env['stock.quant']._update_available_quantity(product1, pack_location, 2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, pack_location), 2.0)
def test_mix_tracked_untracked_1(self):
@@ -499,27 +525,27 @@ class StockQuant(TransactionCase):
})
# add one tracked, one untracked
self.env['stock.quant']._increase_available_quantity(product1, stock_location, 1.0)
self.env['stock.quant']._increase_available_quantity(product1, stock_location, 1.0, lot_id=lot1)
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0)
self.env['stock.quant']._update_available_quantity(product1, stock_location, 1.0, lot_id=lot1)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location, strict=True), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location, lot_id=lot1), 1.0)
self.env['stock.quant']._increase_reserved_quantity(product1, stock_location, 1.0, lot_id=lot1, strict=True)
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, 1.0, lot_id=lot1, strict=True)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location, strict=True), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location, lot_id=lot1), 0.0)
self.env['stock.quant']._decrease_reserved_quantity(product1, stock_location, 1.0, lot_id=lot1, strict=True)
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, -1.0, lot_id=lot1, strict=True)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location, strict=True), 1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location, lot_id=lot1), 1.0)
with self.assertRaises(UserError):
self.env['stock.quant']._decrease_reserved_quantity(product1, stock_location, 1.0)
self.env['stock.quant']._update_reserved_quantity(product1, stock_location, -1.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location), 2.0)
self.assertEqual(self.env['stock.quant']._get_available_quantity(product1, stock_location, strict=True), 1.0)
-67
View File
@@ -58,47 +58,6 @@
</field>
</record>
<record id="action_receive_move" model="ir.actions.act_window">
<field name="name">Receipts</field>
<field name="res_model">stock.move</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain" eval="[('picking_id.picking_type_id.code','=','incoming'), ('location_id.usage','!=','internal'), ('location_dest_id.usage', '=', 'internal')]"/>
<field name="view_id" ref="view_move_tree_receipt_picking"/>
<field name="context" eval="'{\'search_default_product_id\': [active_id]}'"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to register a receipt for this product.
</p><p>
Here you will find the history of all receipts related to
this product, as well as all future receipts you are waiting
from your suppliers.
</p>
</field>
</record>
<record id="action_deliver_move" model="ir.actions.act_window">
<field name="name">Deliveries</field>
<field name="res_model">stock.move</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_move_tree_receipt_picking"/>
<field name="domain" eval="[('picking_type_id.code','=','outgoing'), ('location_id.usage','=','internal'), ('location_dest_id.usage', '!=', 'internal')]"/>
<field name="context" eval="'{\'search_default_product_id\': [active_id]}'"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to add a delivery order for this product.
</p><p>
Here you will find the history of all past deliveries related to
this product, as well as all the products you must deliver to
customers.
</p>
</field>
</record>
<!-- Product Template -->
<record id="product_template_search_form_view_stock" model="ir.ui.view">
@@ -223,32 +182,6 @@
</field>
</record>
<record model="ir.ui.view" id="product_kanban_stock_view">
<field name="name">Product Kanban Stock</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_kanban_view"/>
<field name="arch" type="xml">
<field name="lst_price" position="after">
<field name="reception_count"/>
<field name="delivery_count"/>
</field>
<xpath expr="//div[@name='tags']" position="inside">
<a name="%(action_receive_move)d" type="action" t-if="record.reception_count.raw_value&gt;1">
<t t-esc="record.reception_count.value"/> Receipts
</a>
<a name="%(action_receive_move)d" type="action" t-if="record.reception_count.raw_value==1">
<t t-esc="record.reception_count.value"/> Receipt
</a>
<a name="%(action_deliver_move)d" type="action" t-if="record.delivery_count.raw_value&gt;1">
<t t-esc="record.delivery_count.value"/> Deliveries
</a>
<a name="%(action_deliver_move)d" type="action" t-if="record.delivery_count.raw_value==1">
<t t-esc="record.delivery_count.value"/> Delivery
</a>
</xpath>
</field>
</record>
<record id="product_product_view_form_easy_inherit_stock" model="ir.ui.view">
<field name="name">product.product.view.form.easy.inherit.stock</field>
<field name="model">product.product</field>
+2
View File
@@ -175,6 +175,7 @@
<field name="lot_name" invisible="not context.get('show_lots_text')" groups="stock.group_production_lot"/>
<field name="package_id" invisible="not context.get('show_package')" groups="stock.group_tracking_lot"/>
<field name="result_package_id" groups="stock.group_tracking_lot"/>
<field name="owner_id" groups="stock.group_tracking_owner"/>
<field name="product_uom_qty" readonly="1"/>
<field name="qty_done"/>
<field name="product_uom_id" options="{'no_open': True, 'no_create': True}" attrs="{'readonly': [('product_uom_qty', '!=', 0.0)]}" string="Unit of Measure" groups="product.group_uom"/>
@@ -211,6 +212,7 @@
<field name="qty_done"/>
<field name="product_uom_id" options="{'no_open': True, 'no_create': True}" attrs="{'readonly': [('product_uom_qty', '!=', 0.0)]}" string="Unit of Measure" groups="product.group_uom"/>
<field name="result_package_id" groups="stock.group_tracking_lot"/>
<field name="owner_id" groups="stock.group_tracking_owner"/>
<field name="picking_id" invisible="1"/>
<field name="product_id" invisible="1"/>
</tree>
+3 -4
View File
@@ -244,11 +244,10 @@
<group>
<field name="min_date"/>
<field name="origin" placeholder="e.g. PO0032" class="oe_inline"/>
<!--<label for="owner_id" groups="stock.group_tracking_owner"/>-->
<div groups="stock.group_tracking_owner">
<!--<field name="owner_id"/>-->
<field name="owner_id" groups="stock.group_tracking_owner"/>
<div groups="stock.group_tracking_owner" colspan="2" col="2">
<button name="action_assign_owner" string="Assign Owner" type="object" attrs="{'invisible': ['|',('move_line_exist', '=', False),('state', 'not in', ('draft','assigned','confirmed'))]}"
class="oe_link oe_edit_only"/>
class="oe_link"/>
</div>
</group>
</group>
+1 -1
View File
@@ -43,7 +43,7 @@
</div>
<group>
<group>
<field name="product_id" domain="[('type', '=', 'product')]"/>
<field name="product_id"/>
<label for="scrap_qty"/>
<div class="o_row">
<field name="scrap_qty"/>
@@ -53,7 +53,8 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
*/
on_attach_callback: function() {
this.is_in_DOM = true;
if (this.active_view && this.active_view.controller.on_attach_callback) {
var controller = this.active_view && this.active_view.controller;
if (controller && this.active_view.controller.on_attach_callback) {
this.active_view.controller.on_attach_callback();
}
},
@@ -579,10 +580,11 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
// Wrong default_* and search_default_* values will no give the expected result
// Wrong group_by values will simply fail and forbid rendering of the destination view
var ncontext = new Context(
_.object(_.reject(_.pairs(_.extend({}, action_data.context, self.env.context)), function(pair) {
_.object(_.reject(_.pairs(self.env.context), function(pair) {
return pair[0].match('^(?:(?:default_|search_default_|show_).+|.+_view_ref|group_by|group_by_no_leaf|active_id|active_ids)$') !== null;
}))
);
ncontext.add(action_data.context || {});
ncontext.add({active_model: self.env.modelName});
if (res_ids) {
ncontext.add({
@@ -65,9 +65,12 @@ var AbstractField = Widget.extend({
* @constructor
* @param {Widget} parent
* @param {string} name The field name defined in the model
* @param {Object} record A record object (result of the get method of a basic model)
* @param {Object} record A record object (result of the get method of
* a basic model)
* @param {Object} [options]
* @param {string} [options.mode=readonly] should be 'readonly' or 'edit'
* @param {string} [options.standalone=false] this flag indicates that the
* field does not have a controller to delegate to
*/
init: function (parent, name, record, options) {
this._super(parent);
@@ -157,6 +160,10 @@ var AbstractField = Widget.extend({
// calls to the format (resp. parse) function.
this.formatOptions = {};
this.parseOptions = {};
// standalone is a flag indicating that the field does not have a
// controller to delegate actions to
this.standalone = options.standalone;
},
/**
* When a field widget is appended to the DOM, its start method is called,
+11 -3
View File
@@ -29,12 +29,20 @@ var _t = core._t;
/**
* @todo Really? it returns a jqueryElement... We should try to move this to a
* module with dom helpers functions, such as web.dom, maybe. And replace this
* with a function that returns a string
* with a function that returns a string so we can get rid of the forceString.
*
* @param {boolean} value
* @returns {jQueryElement}
* @param {Object} [field]
* a description of the field (note: this parameter is ignored)
* @param {Object} [options] additional options
* @param {boolean} [options.forceString=false] if true, returns a string
* representation of the boolean rather than a jQueryElement
* @returns {jQueryElement | string}
*/
function formatBoolean(value) {
function formatBoolean(value, field, options) {
if (options && options.forceString) {
return value ? _t('True') : _t('False');
}
var $input = $('<input/>', {
type: 'checkbox',
}).prop({
@@ -223,21 +223,25 @@ var FieldMany2One = AbstractField.extend({
_quickCreate: function (name) {
var self = this;
var slowCreate = this._searchCreatePopup.bind(this, "form", false, this._createContext(name));
var action = function () {
return self._rpc({
model: self.field.relation,
method: 'name_create',
args: [name],
context: self.record.getContext(self.recordParams),
}).then(function (result) {
if (self.mode === "edit") {
self.reinitialize({id: result[0], display_name: result[1]});
}
}, slowCreate);
};
if (this.nodeOptions.quick_create) {
this.trigger_up('mutexify', {
action: function () {
return self._rpc({
model: self.field.relation,
method: 'name_create',
args: [name],
context: self.record.getContext(self.recordParams),
}).then(function (result) {
if (self.mode === "edit") {
self.reinitialize({id: result[0], display_name: result[1]});
}
}, slowCreate);
},
});
if (this.standalone) {
action();
} else {
this.trigger_up('mutexify', {action: action});
}
} else {
slowCreate();
}
@@ -15,6 +15,7 @@ odoo.define('web.AbstractModel', function (require) {
*/
var Class = require('web.Class');
var fieldUtils = require('web.field_utils');
var mixins = require('web.mixins');
var ServicesMixin = require('web.ServicesMixin');
@@ -63,6 +64,30 @@ var AbstractModel = Class.extend(mixins.EventDispatcherMixin, ServicesMixin, {
reload: function (params) {
return $.when();
},
/**
* Processes date(time) and selection field values sent by the server.
* Converts data(time) values to moment instances.
* Converts false values of selection fields to 0 if 0 is a valid key,
* because the server doesn't make a distinction between false and 0, and
* always sends false when value is 0.
*
* @param {Object} field the field description
* @param {*} value
* @returns {*} the processed value
*/
_parseServerValue: function (field, value) {
if (field.type === 'date' || field.type === 'datetime') {
// process date(time): convert into a moment instance
value = fieldUtils.parse[field.type](value, field, {isUTC: true});
} else if (field.type === 'selection' && value === false) {
// process selection: convert false to 0, if 0 is a valid key
var hasKey0 = _.find(field.selection, function (option) {
return option[0] === 0;
});
value = hasKey0 ? 0 : value;
}
return value;
},
});
return AbstractModel;
@@ -84,10 +84,9 @@ odoo.define('web.BasicModel', function (require) {
var AbstractModel = require('web.AbstractModel');
var concurrency = require('web.concurrency');
var core = require('web.core');
var Context = require('web.Context');
var core = require('web.core');
var Domain = require('web.Domain');
var fieldUtils = require('web.field_utils');
var session = require('web.session');
var _t = core._t;
@@ -2370,8 +2369,8 @@ var BasicModel = AbstractModel.extend({
* if given, this field's context is added to the context, instead of
* the element's context (except if options.full is true)
* @param {boolean} [options.full=false]
* if true and fieldName given in options, the element's context
* is added to the context
* if true or nor fieldName or additionalContext given in options,
* the element's context is added to the context
* @returns {Object} the evaluated context
*/
_getContext: function (element, options) {
@@ -2379,7 +2378,7 @@ var BasicModel = AbstractModel.extend({
var context = new Context(session.user_context);
context.set_eval_context(this._getEvalContext(element));
if (options.full || !options.fieldName) {
if (options.full || !(options.fieldName || options.additionalContext)) {
context.add(element.context);
}
if (options.fieldName) {
@@ -2656,8 +2655,14 @@ var BasicModel = AbstractModel.extend({
var type = params.type || ('domain' in params && 'list') || 'record';
var res_id, value;
var res_ids = params.res_ids || [];
var data = params.data || (type === 'record' ? {} : []);
if (type === 'record') {
res_id = params.res_id || (params.data && params.data.id) || _.uniqueId('virtual_');
res_id = params.res_id || (params.data && params.data.id);
if (res_id) {
data.id = res_id;
} else {
res_id = _.uniqueId('virtual_');
}
} else {
var isValueArray = params.value instanceof Array;
res_id = isValueArray ? params.value[0] : undefined;
@@ -2675,7 +2680,7 @@ var BasicModel = AbstractModel.extend({
aggregateValues: params.aggregateValues || {},
context: params.context || {},
count: params.count || res_ids.length,
data: params.data || (type === 'record' ? {} : []),
data: data,
domain: params.domain || [],
fields: fields,
fieldsInfo: params.fieldsInfo,
@@ -2940,30 +2945,6 @@ var BasicModel = AbstractModel.extend({
}
});
},
/**
* Processes date(time) and selection field values sent by the server.
* Converts data(time) values to moment instances.
* Converts false values of selection fields to 0 if 0 is a valid key,
* because the server doesn't make a distinction between false and 0, and
* always sends false when value is 0.
*
* @param {Object} field the field description
* @param {*} value
* @returns {*} the processed value
*/
_parseServerValue: function (field, value) {
if (field.type === 'date' || field.type === 'datetime') {
// process date(time): convert into a moment instance
value = fieldUtils.parse[field.type](value, field, {isUTC: true});
} else if (field.type === 'selection' && value === false) {
// process selection: convert false to 0, if 0 is a valid key
var hasKey0 = _.find(field.selection, function (option) {
return option[0] === 0;
});
value = hasKey0 ? 0 : value;
}
return value;
},
/**
* This method is quite important: it is supposed to perform the /onchange
* rpc and apply the result.
@@ -3087,7 +3068,8 @@ var BasicModel = AbstractModel.extend({
lazy: true,
})
.then(function (groups) {
var rawGroupBy = list.groupedBy[0].split(':')[0];
var groupByField = list.groupedBy[0];
var rawGroupBy = groupByField.split(':')[0];
var previousGroups = _.map(list.data, function (groupID) {
return self.localData[groupID];
});
@@ -3098,13 +3080,13 @@ var BasicModel = AbstractModel.extend({
_.each(groups, function (group) {
var aggregateValues = {};
_.each(group, function (value, key) {
if (_.contains(fields, key) && key !== list.groupedBy[0]) {
if (_.contains(fields, key) && key !== groupByField) {
aggregateValues[key] = value;
}
});
// When a view is grouped, we need to display the name of each group in
// the 'title'.
var value = group[rawGroupBy];
var value = group[groupByField];
if (list.fields[rawGroupBy].type === "selection") {
var choice = _.find(list.fields[rawGroupBy].selection, function (c) {
return c[0] === value;
@@ -406,12 +406,13 @@ return AbstractModel.extend({
});
}
return eventsDef.then(function (events) {
self.data.data = _.map(events, self._recordToCalendarEvent.bind(self));
return $.when(
self._loadColors(self.data, self.data.data),
self._loadRecordsToFilters(self.data, self.data.data)
);
});
self._parseServerData(events);
self.data.data = _.map(events, self._recordToCalendarEvent.bind(self));
return $.when(
self._loadColors(self.data, self.data.data),
self._loadRecordsToFilters(self.data, self.data.data)
);
});
});
},
/**
@@ -566,11 +567,24 @@ return AbstractModel.extend({
if (filter.filters.length && (filter.filters[0].avatar_model in to_read)) {
_.each(filter.filters, function (f) {
f.label = to_read[f.avatar_model][f.value];
});
});
}
});
});
},
/**
* parse the server values to javascript framwork
*
* @param {Object} data the server data to parse
*/
_parseServerData: function (data) {
var self = this;
_.each(data, function(event) {
_.each(self.fieldNames, function (fieldName) {
event[fieldName] = self._parseServerValue(self.fields[fieldName], event[fieldName]);
});
});
},
/**
* Transform OpenERP event object to fullcalendar event object
*/
@@ -583,11 +597,11 @@ return AbstractModel.extend({
attendees = [];
if (!all_day) {
date_start = fieldUtils.parse.datetime(evt[this.mapping.date_start]);
date_stop = this.mapping.date_stop ? fieldUtils.parse.datetime(evt[this.mapping.date_stop]) : null;
date_start = evt[this.mapping.date_start].clone();
date_stop = this.mapping.date_stop ? evt[this.mapping.date_stop].clone() : null;
} else {
date_start = fieldUtils.parse.datetime(evt[this.mapping.date_start].split(' ')[0]);
date_stop = this.mapping.date_stop ? fieldUtils.parse.datetime(evt[this.mapping.date_stop].split(' ')[0]) : null;
date_start = evt[this.mapping.date_start].clone().startOf('day');
date_stop = this.mapping.date_stop ? evt[this.mapping.date_stop].clone().startOf('day') : null;
}
if (!date_stop && date_delay) {
@@ -605,8 +619,8 @@ return AbstractModel.extend({
'record': evt,
'start': date_start,
'end': date_stop,
'r_start':date_start.clone(),
'r_end':date_stop.clone(),
'r_start': date_start,
'r_end': date_stop,
'title': the_title,
'allDay': this.mapping.all_day && evt[this.mapping.all_day] || false,
'id': evt.id,
@@ -288,7 +288,7 @@ return AbstractRenderer.extend({
if (field.type === "one2many" || field.type === "many2many") {
return field_utils.format[field.type]({data: record[fieldName]}, field);
} else {
return field_utils.format[field.type](record[fieldName], field);
return field_utils.format[field.type](record[fieldName], field, {forceString: true});
}
},
/**
@@ -164,9 +164,10 @@ var KanbanController = BasicController.extend({
_onAddColumn: function (event) {
var self = this;
this.model.createGroup(event.data.value, this.handle).then(function () {
self.update({}, {reload: false});
return self.update({}, {reload: false});
}).then(function () {
self._updateButtons();
self.trigger_up('scrollTo', {selector: '.o_column_quick_create'});
self.renderer.quickCreateToggleFold();
});
},
/**
@@ -192,7 +192,7 @@ var RecordQuickCreate = AbstractQuickCreate.extend({
var ColumnQuickCreate = AbstractQuickCreate.extend({
template: 'KanbanView.ColumnQuickCreate',
events: _.extend({}, AbstractQuickCreate.prototype.events, {
'click': '_onClick',
'click': 'toggleFold',
'click input': '_onInputClicked',
'focusout': '_onFocusout',
}),
@@ -213,6 +213,22 @@ var ColumnQuickCreate = AbstractQuickCreate.extend({
return this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* Toggle fold/unfold the Column quick create widget
*/
toggleFold: function () {
this.folded = !this.folded;
this._update();
if (!this.folded) {
this.$input.focus();
this.trigger_up('scrollTo', {selector: '.o_column_quick_create'});
}
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
@@ -241,18 +257,6 @@ var ColumnQuickCreate = AbstractQuickCreate.extend({
// Handlers
//--------------------------------------------------------------------------
/**
* @private
* @param {MouseEvent} event
*/
_onClick: function () {
this.folded = !this.folded;
this._update();
if (!this.folded) {
this.$input.focus();
this.trigger_up('scrollTo', {selector: '.o_column_quick_create'});
}
},
/**
* @private
*/
@@ -112,6 +112,12 @@ var KanbanRenderer = BasicRenderer.extend({
addQuickCreate: function () {
this.widgets[0].addQuickCreate();
},
/**
* Toggle fold/unfold the Column quick create widget
*/
quickCreateToggleFold: function () {
this.quickCreate.toggleFold();
},
/**
* Removes a widget (record if ungrouped, column if grouped) from the view.
*
@@ -244,8 +250,8 @@ var KanbanRenderer = BasicRenderer.extend({
// Enable column quickcreate
if (this.columnOptions.group_creatable) {
var quickCreate = new ColumnQuickCreate(this);
quickCreate.appendTo(fragment);
this.quickCreate = new ColumnQuickCreate(this);
this.quickCreate.appendTo(fragment);
}
}
@@ -377,6 +377,10 @@ var ListRenderer = BasicRenderer.extend({
$cells.unshift($('<td>'));
}
var name = group.value === undefined ? _t('Undefined') : group.value;
var groupBy = this.state.groupedBy[groupLevel];
if (group.fields[groupBy.split(':')[0]].type !== 'boolean') {
name = name || _t('Undefined');
}
var $th = $('<th>')
.addClass('o_group_name')
.text(name + ' (' + group.count + ')');
+1 -1
View File
@@ -127,7 +127,7 @@
}
}
> .o_field_translate {
position: absolute;
.o-position-absolute(0, 5px);
width: 16px; // to have some space on the right
}
.o_input {
@@ -1,11 +1,13 @@
odoo.define('web.relational_fields_tests', function (require) {
"use strict";
var BasicModel = require('web.BasicModel');
var concurrency = require('web.concurrency');
var FormView = require('web.FormView');
var ListView = require('web.ListView');
var relationalFields = require('web.relational_fields');
var testUtils = require('web.test_utils');
var Widget = require('web.Widget');
var createView = testUtils.createView;
@@ -361,7 +363,6 @@ QUnit.module('relational_fields', {
form.destroy();
});
QUnit.test('many2one readonly fields with option "no_open"', function (assert) {
assert.expect(1);
@@ -564,10 +565,71 @@ QUnit.module('relational_fields', {
form.destroy();
});
QUnit.test('many2one field with option standalone', function (assert) {
assert.expect(3);
var done = assert.async();
var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY;
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0;
var fixture = $('#qunit-fixture');
var self = this;
var model = testUtils.createModel({
Model: BasicModel,
data: this.data,
});
model.makeRecord('coucou', [{
name: 'partner_id',
relation: 'partner',
type: 'many2one',
value: [1, 'first partner'],
}], {
partner_id: {
options: {
no_open: true,
},
},
}).then(function (recordID) {
var record = model.get(recordID);
var parent = new Widget();
testUtils.addMockEnvironment(parent, {
data: self.data,
mockRPC: function (route, args) {
assert.step(args.method);
return this._super.apply(this, arguments);
},
});
var relField = new relationalFields.FieldMany2One(parent,
'partner_id',
record,
{
mode: 'edit',
standalone: true,
});
relField.appendTo(fixture);
$('input.o_input').val('xyzzrot').trigger('input');
concurrency.delay(0).then(function () {
var $dropdown = $('input.o_input').autocomplete('widget');
$dropdown.find('.o_m2o_dropdown_option:contains(Create)')
.first().mouseenter().click();
assert.verifySteps(['name_search', 'name_create']);
parent.destroy();
model.destroy();
relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY;
done();
});
});
});
// QUnit.test('onchange on a many2one to a different model', function (assert) {
// This test is commented because the mock server does not give the correct response.
// It should return a couple [id, display_name], but I don't know the logic used
// by the server, so it's hard to emulate it correctly
// This test is commented because the mock server does not give the correct response.
// It should return a couple [id, display_name], but I don't know the logic used
// by the server, so it's hard to emulate it correctly
// assert.expect(2);
// this.data.partner.records[0].product_id = 41;
@@ -995,14 +1057,16 @@ QUnit.module('relational_fields', {
assert.deepEqual(
args.kwargs.context,
{hey: "ho", hello: "world", test: "yop"},
'the field attr context should have been used for the '
+ 'RPC (evaluated and merged with the session one)');
'the field attr context should have been used for the ' +
'RPC (evaluated and merged with the session one)');
return $.when([]);
}
if (args.method === 'name_search' && args.model === 'partner') {
assert.deepEqual(args.kwargs.args, [['id', 'in', [12]]],
'the field attr domain should have been used for the RPC (and evaluated)');
assert.deepEqual(args.kwargs.context, {hey: 'ho', timmy: [[6, false, [12]]]},
'the field attr context should have been used for the RPC (and evaluated)');
return $.when([]);
}
return this._super.apply(this, arguments);
},
@@ -3652,6 +3716,34 @@ QUnit.module('relational_fields', {
form.destroy();
});
QUnit.test('id in one2many obtained in onchange is properly set', function (assert) {
assert.expect(1);
this.data.partner.onchanges.turtles = function (obj) {
obj.turtles = [
[5],
[1, 3, {turtle_foo: "kawa"}]
];
};
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="turtles">' +
'<tree>' +
'<field name="id"/>' +
'<field name="turtle_foo"/>' +
'</tree>' +
'</field>' +
'</form>',
});
assert.strictEqual(form.$('tr.o_data_row').text(), '3kawa',
"should have properly displayed id and foo field");
form.destroy();
});
QUnit.test('sub form view with a required field', function (assert) {
assert.expect(2);
this.data.partner.fields.foo.required = true;
+4 -20
View File
@@ -465,25 +465,7 @@ var MockServer = Class.extend({
_mockNameSearch: function (model, args, _kwargs) {
var str = args && typeof args[0] === 'string' ? args[0] : _kwargs.name;
var domain = (args && args[1]) || _kwargs.args || [];
var dom = domain[0];
var records = this.data[model].records;
if (dom) {
records = _.filter(records, function (record) {
var value = record[dom[0]];
if (value instanceof Array) {
value = value[0];
}
if (dom[1] === 'not in') {
return !_.contains(dom[2], value);
} else if (dom[1] === 'in') {
return _.contains(dom[2], value);
} else if (dom[1] === '==') {
return dom[2] == value;
} else if (dom[1] === '!=') {
return dom[2] != value;
}
});
}
var records = this._getRecords(model, domain);
if (str.length) {
records = _.filter(records, function (record) {
return record.display_name.indexOf(str) !== -1;
@@ -632,7 +614,9 @@ var MockServer = Class.extend({
var fieldName = groupByField.split(':')[0];
var aggregateFunction = groupByField.split(':')[1] || 'month';
if (fields[fieldName].type === 'date') {
if (aggregateFunction === 'day') {
if (!val) {
return false;
} else if (aggregateFunction === 'day') {
return moment(val).format('YYYY-MM-DD');
} else {
return moment(val).format('MMMM YYYY');
@@ -3,6 +3,7 @@ odoo.define('web.calendar_tests', function (require) {
var CalendarView = require('web.CalendarView');
var CalendarRenderer = require('web.CalendarRenderer');
var fieldUtils = require('web.field_utils');
var testUtils = require('web.test_utils');
var session = require('web.session');
@@ -431,6 +432,8 @@ QUnit.module('Views', {
'mode="week" '+
'readonly_form_view_id="1">'+
'<field name="name"/>'+
'<field name="start"/>'+
'<field name="allday"/>'+
'</calendar>',
archs: archs,
viewOptions: {
@@ -478,14 +481,14 @@ QUnit.module('Views', {
$('.modal button.btn:contains(Create)').trigger('click');
var $newevent = calendar.$('.fc-event:contains(new event)');
assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "08:00-10:00newevent",
"should display the new event with time and title");
assert.strictEqual($newevent.text().replace(/[\s\n\r]+/g, ''), "08:00-10:00newevent12/13/201608:00:00False",
"should display the new event with time, title and additional fields");
assert.deepEqual($newevent.data('fcSeg').event.record,
{
display_name: "new event",
start: "2016-12-13 06:00:00",
stop: "2016-12-13 08:00:00",
start: fieldUtils.parse.datetime("2016-12-13 06:00:00", this.data.event.fields.start, {isUTC: true}),
stop: fieldUtils.parse.datetime("2016-12-13 08:00:00", this.data.event.fields.stop, {isUTC: true}),
allday: false,
name: "new event",
id: 1
@@ -616,8 +619,8 @@ QUnit.module('Views', {
assert.deepEqual($newevent.data('fcSeg').event.record,
{
display_name: "new event",
start: "2016-12-13 06:00:00",
stop: "2016-12-13 08:00:00",
start: fieldUtils.parse.datetime("2016-12-13 06:00:00", this.data.event.fields.start, {isUTC: true}),
stop: fieldUtils.parse.datetime("2016-12-13 08:00:00", this.data.event.fields.stop, {isUTC: true}),
allday: false,
name: "new event",
id: 1
@@ -691,8 +694,8 @@ QUnit.module('Views', {
assert.deepEqual($newevent.data('fcSeg').event.record,
{
display_name: "new event",
start: "2016-12-14 00:00:00",
stop: "2016-12-15 00:00:00",
start: fieldUtils.parse.datetime("2016-12-14 00:00:00", this.data.event.fields.start, {isUTC: true}),
stop: fieldUtils.parse.datetime("2016-12-15 00:00:00", this.data.event.fields.stop, {isUTC: true}),
allday: true,
name: "new event",
id: 1
+4 -1
View File
@@ -3555,10 +3555,13 @@ QUnit.module('Views', {
'</sheet>' +
'</form>',
res_id: 2,
viewOptions: {
context: {some_context: true},
},
intercepts: {
execute_action: function (e) {
assert.deepEqual(e.data.action_data.context, {test: 2},
"button context should have been evaluated and given to the action");
"button context should have been evaluated and given to the action, without previous context");
},
},
});
@@ -1626,6 +1626,35 @@ QUnit.module('Views', {
"column should contain 1 records");
kanban.destroy();
});
QUnit.test('don\'t fold column quick create after creation', function (assert) {
assert.expect(2);
var kanban = createView({
View: KanbanView,
model: 'partner',
data: this.data,
arch: '<kanban on_create="quick_create">' +
'<field name="product_id"/>' +
'<templates><t t-name="kanban-box">' +
'<div><field name="foo"/></div>' +
'</t></templates>' +
'</kanban>',
groupBy: ['product_id'],
});
// add a new column
kanban.$('.o_kanban_group:first .o_kanban_quick_add').click();
var $quickCreate = kanban.$('.o_kanban_quick_create');
$quickCreate.find('input').val('new partner');
$quickCreate.find('button.o_kanban_add').click();
assert.strictEqual(this.data.partner.records.length, 5,
"should have created a 'new partner' column");
assert.strictEqual(kanban.$('.o_kanban_add:visible').length, 1,
"the add button should still be visible");
kanban.destroy();
});
});
});
@@ -2466,6 +2466,23 @@ QUnit.module('Views', {
list.destroy();
});
QUnit.test('list grouped by date:month', function (assert) {
assert.expect(1);
var list = createView({
View: ListView,
model: 'foo',
data: this.data,
arch: '<tree><field name="date"/></tree>',
groupBy: ['date:month'],
});
assert.strictEqual(list.$('tbody').text(), "January 2017 (1)Undefined (3)",
"the group names should be correct");
list.destroy();
});
});
});
@@ -134,12 +134,18 @@ if(!$('.o_website_quote').length) {
case "h1":
var id = self.setElementId('quote_header_', el);
var text = self.extractText($(el));
if (!text) {
break;
}
last_li = $("<li>").append($('<a href="#'+id+'"/>').text(text)).appendTo(self.$el);
last_ul = false;
break;
case "h2":
var id = self.setElementId('quote_', el);
var text = self.extractText($(el));
if (!text) {
break;
}
if (last_li) {
if (!last_ul) {
last_ul = $("<ul class='nav'>").appendTo(last_li);
+2 -2
View File
@@ -174,7 +174,7 @@
<div class="products_pager">
<div class="row" style="width: 100%;">
<t t-call="website_sale.search">
<t t-set="search_class">pagination form-inline o_website_sale_search</t>
<t t-set="search_class" t-value="'pagination form-inline o_website_sale_search'"/>
</t>
<t t-call="website_sale.pricelist_list" />
<t t-call="website.pager" />
@@ -1258,7 +1258,7 @@
<div id="wrap">
<div class="container oe_website_sale">
<div class="row">
<div class="col-lg-8 col-sm-9">
<div class="col-md-offset-2 col-lg-8 col-sm-9">
<t t-foreach="errors" t-as="error">
<div class="alert alert-danger" t-if="error">
<h4>
@@ -44,8 +44,14 @@ class WebsiteSaleDelivery(WebsiteSale):
has_stockable_products = any(line.product_id.type in ['consu', 'product'] for line in order.order_line)
if has_stockable_products:
if order.carrier_id and not order.delivery_rating_success:
values['errors'].append(
(_("Ouch, you cannot choose this carrier!"),
_("%s does not ship to your address, please choose another one.") % order.carrier_id.name))
order._remove_delivery_line()
delivery_carriers = order._get_delivery_methods()
values['deliveries'] = delivery_carriers.sudo().with_context(order_id=order.id)
values['deliveries'] = delivery_carriers.sudo()
values['delivery_action_id'] = request.env.ref('delivery.action_delivery_carrier_form').id
return values
@@ -2,8 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from odoo import api, fields, models
_logger = logging.getLogger(__name__)
@@ -61,7 +60,8 @@ class SaleOrder(models.Model):
self.write({'carrier_id': carrier.id})
if carrier:
self.get_delivery_price()
self.set_delivery_line()
if self.delivery_rating_success:
self.set_delivery_line()
else:
self._remove_delivery_line()
@@ -85,8 +85,4 @@ class SaleOrder(models.Model):
values = super(SaleOrder, self)._cart_update(product_id, line_id, add_qty, set_qty, **kwargs)
if add_qty or set_qty is not None:
for sale_order in self:
self._check_carrier_quotation()
return values
@@ -1,3 +1,4 @@
'use strict';
$(document).ready(function () {
// When choosing an delivery carrier, update the quotation and the acquirers
@@ -11,7 +12,7 @@ $(document).ready(function () {
var value = $(this).val();
var $provider_free = $("select[name='country_id']:not(.o_provider_restricted), select[name='state_id']:not(.o_provider_restricted)");
var $provider_restricted = $("select[name='country_id'].o_provider_restricted, select[name='state_id'].o_provider_restricted");
if (value == 0) {
if (value === 0) {
// Ship to the same address : only show shipping countries available for billing
$provider_free.hide().attr('disabled', true);
$provider_restricted.show().attr('disabled', false).change();
@@ -25,20 +25,14 @@
<xpath expr="//div[@id='payment_method']" position="before">
<div t-if="deliveries" class="col-md-12 row" id="delivery_carrier">
<div class="col-lg-7 col-sm-8">
<h4>Choose a delivery method</h4>
<h4>Choose a delivery method <i class="fa fa-question text-muted" title="Delivery price will be updated after choosing a new delivery method" aria-hidden="true"></i></h4>
<ul class="list-unstyled">
<li t-foreach="deliveries" t-as="delivery">
<div id="delivery_method">
<label>
<input t-att-value="delivery.id" type="radio" name="delivery_type"
t-att-checked="order.carrier_id and order.carrier_id.id == delivery.id and 'checked' or False" disabled="1"/>
t-att-checked="order.carrier_id and order.carrier_id.id == delivery.id and 'checked' or False"/>
<abbr t-att-title="delivery.website_description"><span t-field="delivery.name"/></abbr>
<span class="badge" t-field="delivery.price"
t-options='{
"widget": "monetary",
"from_currency": delivery.product_id.company_id.currency_id,
"display_currency": website_sale_order.currency_id
}'/>
<div t-field="delivery.website_description" class="text-muted"/>
</label>
</div>
+14
View File
@@ -0,0 +1,14 @@
Finland, 2017-07-17
Oy Tawasta OS Technologies Ltd. agrees to the terms of the Odoo Corporate Contributor License Agreement v1.0.
We declare that we are authorized and able to make this agreement and sign this declaration.
Signed,
Jarmo Kortetjärvi jarmo.kortetjarvi@tawasta.fi https://github.com/jarmokortetjarvi
List of contributors:
Aleksi Savijoki aleksi.savijoki@tawasta.fi https://github.com/savijoki
Jarmo Kortetjärvi jarmo.kortetjarvi@tawasta.fi https://github.com/jarmokortetjarvi
+10
View File
@@ -0,0 +1,10 @@
India, 2017-07-04
I hereby agree to the terms of the Odoo Individual Contributor License Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this declaration.
Signed,
Rakesh Sindhav sindhav21591@gmail.com https://github.com/sindhav
+11
View File
@@ -0,0 +1,11 @@
Finland, 2017-07-19
I hereby agree to the terms of the Odoo Individual Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Timo Talvitie timo.talvitie@vizucom.com https://github.com/timotalvitie
+1 -1
View File
@@ -11,7 +11,7 @@ diff --git a/openacademy/models.py b/openacademy/models.py
+from datetime import timedelta
from odoo import models, fields, api, exceptions
class Session(models.Model):
class Course(models.Model):
@@ -55,6 +56,8 @@ class Session(models.Model):
attendee_ids = fields.Many2many('res.partner', string="Attendees")
+1 -1
View File
@@ -17,7 +17,7 @@ Index: addons/openacademy/views/openacademy.xml
===================================================================
--- addons.orig/openacademy/views/openacademy.xml 2014-08-28 14:21:58.627015606 +0200
+++ addons/openacademy/views/openacademy.xml 2014-08-28 14:21:58.623015606 +0200
@@ -169,11 +169,57 @@
@@ -168,11 +168,57 @@
</field>
</record>
+2 -2
View File
@@ -30,11 +30,11 @@ dictionary, where each key specifies module metadatum.
""",
# data files always loaded at installation
'data': [
'mymodule_view.xml',
'views/mymodule_view.xml',
],
# data files containing optionally loaded demonstration data
'demo': [
'demo_data.xml',
'demo/demo_data.xml',
],
}
+22 -16
View File
@@ -711,19 +711,12 @@ class IrModelFields(models.Model):
res.append((field.id, '%s (%s)' % (field.field_description, field.model)))
return res
@tools.ormcache()
def _all_field_data(self):
""" Return all fields data, indexed by model name, then field name. """
cr = self._cr
cr.execute("SELECT * FROM ir_model_fields")
result = defaultdict(dict)
for row in cr.dictfetchall():
result[row['model']][row['name']] = row
return result
def _get_field_data(self, model_name):
@tools.ormcache('model_name')
def _existing_field_data(self, model_name):
""" Return the given model's existing field data. """
return self._all_field_data()[model_name]
cr = self._cr
cr.execute("SELECT * FROM ir_model_fields WHERE model=%s", [model_name])
return {row['name']: row for row in cr.dictfetchall()}
def _reflect_field_params(self, field):
""" Return the values to write to the database for the given field. """
@@ -753,7 +746,7 @@ class IrModelFields(models.Model):
def _reflect_field(self, field):
""" Reflect the given field and return its corresponding record. """
fields_data = self._get_field_data(field.model_name)
fields_data = self._existing_field_data(field.model_name)
field_data = fields_data.get(field.name)
params = self._reflect_field_params(field)
@@ -801,7 +794,7 @@ class IrModelFields(models.Model):
if not self.pool._init:
# remove ir.model.fields that are not in self._fields
fields_data = self._get_field_data(model._name)
fields_data = self._existing_field_data(model._name)
extra_names = set(fields_data) - set(model._fields)
if extra_names:
# add key MODULE_UNINSTALL_FLAG in context to (1) force the
@@ -809,6 +802,19 @@ class IrModelFields(models.Model):
records = self.browse([fields_data.pop(name)['id'] for name in extra_names])
records.with_context(**{MODULE_UNINSTALL_FLAG: True}).unlink()
@tools.ormcache()
def _all_manual_field_data(self):
cr = self._cr
cr.execute("SELECT * FROM ir_model_fields WHERE state='manual'")
result = defaultdict(dict)
for row in cr.dictfetchall():
result[row['model']][row['name']] = row
return result
def _get_manual_field_data(self, model_name):
""" Return the given model's manual field data. """
return self._all_manual_field_data().get(model_name, {})
def _instanciate_attrs(self, field_data):
""" Return the parameters for a field instance for ``field_data``. """
attrs = {
@@ -837,7 +843,7 @@ class IrModelFields(models.Model):
if not self.pool.loaded and not (
field_data['relation'] in self.env and (
field_data['relation_field'] in self.env[field_data['relation']]._fields or
field_data['relation_field'] in self._get_field_data(field_data['relation'])
field_data['relation_field'] in self._get_manual_field_data(field_data['relation'])
)):
return
attrs['comodel_name'] = field_data['relation']
@@ -865,7 +871,7 @@ class IrModelFields(models.Model):
def _add_manual_fields(self, model):
""" Add extra fields on model. """
fields_data = self._get_field_data(model._name)
fields_data = self._get_manual_field_data(model._name)
for name, field_data in pycompat.items(fields_data):
if name not in model._fields and field_data['state'] == 'manual':
field = self._instanciate(field_data)
+1 -1
View File
@@ -444,7 +444,7 @@
<div class="o_kanban_tags_section oe_kanban_partner_categories"/>
<div class="o_kanban_image">
<t t-if="record.image_small.raw_value">
<img t-att-src="kanban_image('res.partner', 'image_small', record.id.value)"/>
<img t-att-src="kanban_image('res.partner', 'image_small', record.id.raw_value)"/>
</t>
<t t-if="!record.image_small.raw_value">
<t t-if="record.type.raw_value === 'delivery'">
+1 -1
View File
@@ -12,7 +12,7 @@ RELEASE_LEVELS_DISPLAY = {ALPHA: ALPHA,
# properly comparable using normal operarors, for example:
# (6,1,0,'beta',0) < (6,1,0,'candidate',1) < (6,1,0,'candidate',2)
# (6,1,0,'candidate',2) < (6,1,0,'final',0) < (6,1,2,'final',0)
version_info = (11, 0, 0, ALPHA, 1, '')
version_info = (10, 'saas~17', 0, FINAL, 0, '')
version = '.'.join(str(s) for s in version_info[:2]) + RELEASE_LEVELS_DISPLAY[version_info[3]] + str(version_info[4] or '') + version_info[5]
series = serie = major_version = '.'.join(str(s) for s in version_info[:2])