[MERGE] forward port branch saas-16 up to 4c410ee6a1

This commit is contained in:
Christophe Simonis
2018-01-09 17:03:07 +01:00
27 changed files with 1643 additions and 1493 deletions
+4 -1
View File
@@ -290,7 +290,8 @@ class AccountChartTemplate(models.Model):
tmp1, tmp2 = self.parent_id._install_template(company, code_digits=code_digits, transfer_account_id=transfer_account_id, acc_ref=acc_ref, taxes_ref=taxes_ref)
acc_ref.update(tmp1)
taxes_ref.update(tmp2)
tmp1, tmp2 = self._load_template(company, code_digits=code_digits, transfer_account_id=transfer_account_id, account_ref=acc_ref, taxes_ref=taxes_ref)
# Ensure, even if individually, that everything is translated according to the company's language.
tmp1, tmp2 = self.with_context(lang=company.partner_id.lang)._load_template(company, code_digits=code_digits, transfer_account_id=transfer_account_id, account_ref=acc_ref, taxes_ref=taxes_ref)
acc_ref.update(tmp1)
taxes_ref.update(tmp2)
return acc_ref, taxes_ref
@@ -856,6 +857,8 @@ class WizardMultiChartsAccounts(models.TransientModel):
all the provided information to create the accounts, the banks, the journals, the taxes, the
accounting properties... accordingly for the chosen company.
'''
# Ensure everything is translated consitingly to the company's language, not the user's one.
self = self.with_context(lang=self.company_id.partner_id.lang)
if not self.env.user._is_admin():
raise AccessError(_("Only administrators can change the settings"))
@@ -73,8 +73,8 @@ class HrHolidaySummaryReport(models.AbstractModel):
for index in range(0, ((date_to - date_from).days + 1)):
if date_from >= start_date and date_from <= end_date:
res[(date_from-start_date).days]['color'] = holiday.holiday_status_id.color_name
count+=1
date_from += timedelta(1)
count += abs(holiday.number_of_days)
self.sum = count
return res
File diff suppressed because it is too large Load Diff
@@ -55,7 +55,7 @@
<field name="parent_id" ref="hr_payroll.ALW"/>
</record>
<record id="hr_payroll_head_pp" model="hr.salary.rule.category">
<field name="name">Withholding Tax Grid 2017 (Precompte Professionnel Bareme 2017)</field>
<field name="name">Withholding Tax Grid 2018 (Precompte Professionnel Bareme 2018)</field>
<field name="code">PP</field>
<field name="parent_id" ref="hr_payroll.DED"/>
</record>
@@ -126,14 +126,14 @@ if contract.mobile:
<field name="code">EmpBonus.1</field>
<field name="sequence">42</field>
<field name="condition_select">python</field>
<field name="condition_python">result = categories.BRUT &lt;= 2431.27</field>
<field name="condition_python">result = categories.BRUT &lt;= 2510.47</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">
salary = categories.BRUT
if salary &lt;= 1577.89:
result = 193.79
elif salary &lt;= 2461.27:
result = 193.79 - (0.2194 * (salary - 1577.89))
if salary &lt;= 1609.47:
result = 197.67
elif salary &lt;= 2510.47:
result = 197.67 - (0.2194 * (salary - 1609.47))
</field>
</record>
<record id="hr_salary_rule_employment_bonus_workers" model="hr.salary.rule">
@@ -142,14 +142,14 @@ elif salary &lt;= 2461.27:
<field name="code">EmpBonus.2</field>
<field name="sequence">42</field>
<field name="condition_select">python</field>
<field name="condition_python">result = categories.BRUT + categories.ONSS &lt;= 2431.27</field>
<field name="condition_python">result = categories.BRUT + categories.ONSS &lt;= 2510.47</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">
salary = categories.BRUT
if salary &lt;= 1577.89:
result = 209.29
elif salary &lt;= 2461.27:
result = 209.29 - (0.2369 * (salary - 1577.89))
if salary &lt;= 1609.47:
result = 213.48
elif salary &lt;= 2510.47:
result = 213.48 - (0.2369 * (salary - 1609.47))
</field>
</record>
<record id="hr_payroll_rules_mis_ex_onss_1" model="hr.salary.rule">
@@ -160,7 +160,7 @@ elif salary &lt;= 2461.27:
<field name="sequence">165</field>
<field name="condition_select">python</field>
<field name="appears_on_payslip" eval="False"/>
<field name="condition_python">result = (((employee.marital=='single') or (employee.marital=='married' and employee.spouse_fiscal_status=='without income')) and (employee.resident_bool!=True))</field>
<field name="condition_python">result = (((employee.marital in ['divorced', 'single', 'widower']) or (employee.marital=='married' and employee.spouse_fiscal_status=='without income')) and (employee.resident_bool!=True))</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">
wage = categories.BASIC
@@ -208,7 +208,7 @@ else:
<field name="sequence">120</field>
<field name="condition_select">python</field>
<field name="appears_on_payslip" eval="False"/>
<field name="condition_python">result = (((employee.marital=='single') or (employee.marital=='married' and employee.spouse_fiscal_status=='with income')) and (employee.resident_bool!=True))</field>
<field name="condition_python">result = (((employee.marital in ['divorced', 'single', 'widower']) or (employee.marital=='married' and employee.spouse_fiscal_status=='with income')) and (employee.resident_bool!=True))</field>
</record>
<record id="hr_payroll_rules_baremeII" model="hr.salary.rule">
<field name="category_id" ref="hr_payroll_head_pp"/>
@@ -230,18 +230,6 @@ else:
<field name="condition_python">result = employee.resident_bool==True</field>
<field name="appears_on_payslip" eval="False"/>
</record>
<record id="hr_salary_rule_pp_reduction_low_salary" model="hr.salary.rule">
<field name="category_id" ref="hr_salary_rule_pp_reductions"/>
<field name="name">Withholding Tax Reduction for Low Salaries</field>
<field name="code">PPRed.0</field>
<field name="sequence">131</field>
<field name="condition_select">python</field>
<field name="condition_python">result = categories.GROSS &lt;= 2027.86 and categories.GROSS &gt; 565.02</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">
result = 5.83
</field>
</record>
<record id="hr_salary_rule_pp_reduction_employees" model="hr.salary.rule">
<field name="category_id" ref="hr_salary_rule_pp_reductions"/>
<field name="name">Withholding Tax Reduction for Low Salaries (ONSS)</field>
@@ -318,7 +306,7 @@ elif salary &lt;= 2461.27:
<field name="code">Red.Iso</field>
<field name="sequence">144</field>
<field name="condition_select">python</field>
<field name="condition_python">result = (employee.marital == 'single') and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA &lt; 0.0)</field>
<field name="condition_python">result = (employee.marital in ['divorced', 'single', 'widower']) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA &lt; 0.0)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result = min(25.00, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
</record>
@@ -331,7 +319,7 @@ elif salary &lt;= 2461.27:
<field name="condition_select">python</field>
<field name="condition_python">result = (employee.marital == 'widower' or (employee.marital in ['divorced', 'single', 'widower'] and employee.dependent_children)) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA &lt; 0.0)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result = min(35.00, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
<field name="amount_python_compute">result = min(36.00, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
</record>
<record id="hr_salary_rule_reduction_disabled" model="hr.salary.rule">
<field name="category_id" ref='hr_salary_rule_category_family_reductions'/>
@@ -342,7 +330,7 @@ elif salary &lt;= 2461.27:
<field name="condition_select">python</field>
<field name="condition_python">result = (employee.disabled) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA &lt; 0.0)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result = min(35.00, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
<field name="amount_python_compute">result = min(36.00, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
</record>
<record id="hr_salary_rule_reduction_seniors" model="hr.salary.rule">
<field name="category_id" ref='hr_salary_rule_category_family_reductions'/>
@@ -353,7 +341,7 @@ elif salary &lt;= 2461.27:
<field name="condition_select">python</field>
<field name="condition_python">result = (employee.other_dependent_people and employee.dependent_seniors) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA &lt; 0.0)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result = min(71.0 * employee.dependent_seniors, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
<field name="amount_python_compute">result = min(73.0 * employee.dependent_seniors, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
</record>
<record id="hr_salary_rule_reduction_juniors" model="hr.salary.rule">
<field name="category_id" ref='hr_salary_rule_category_family_reductions'/>
@@ -364,7 +352,7 @@ elif salary &lt;= 2461.27:
<field name="condition_select">python</field>
<field name="condition_python">result = (employee.other_dependent_people and employee.dependent_juniors) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA &lt; 0.0)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result = min(35.0 * employee.dependent_juniors, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
<field name="amount_python_compute">result = min(36.0 * employee.dependent_juniors, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
</record>
<record id="hr_salary_rule_reduction_insufficient_net" model="hr.salary.rule">
<field name="category_id" ref='hr_salary_rule_category_family_reductions'/>
@@ -373,7 +361,7 @@ elif salary &lt;= 2461.27:
<field name="code">Red.Spouse.Net</field>
<field name="sequence">148</field>
<field name="condition_select">python</field>
<field name="condition_python">result = (employee.marital == 'married' and employee.spouse_fiscal_status=='with income' and employee.spouse_net_revenue &lt;= 221.0) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA &lt; 0.0)</field>
<field name="condition_python">result = (employee.marital == 'married' and employee.spouse_fiscal_status=='with income' and employee.spouse_net_revenue &lt;= 225.0) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA &lt; 0.0)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result = min(110.50, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
</record>
@@ -384,7 +372,7 @@ elif salary &lt;= 2461.27:
<field name="code">Red.Spouse.Oth.Net</field>
<field name="sequence">149</field>
<field name="condition_select">python</field>
<field name="condition_python">result = (employee.marital == 'married' and employee.spouse_fiscal_status=='with income' and not employee.spouse_net_revenue and employee.spouse_other_net_revenue &lt;= 440.0) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA &lt; 0.0)</field>
<field name="condition_python">result = (employee.marital == 'married' and employee.spouse_fiscal_status=='with income' and not employee.spouse_net_revenue and employee.spouse_other_net_revenue &lt;= 450.0) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA &lt; 0.0)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">result = min(220.0, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
</record>
@@ -397,7 +385,7 @@ elif salary &lt;= 2461.27:
<field name="condition_select">python</field>
<field name="condition_python">result = (employee.disabled_spouse_bool==True) and (categories.PP + categories.PPRed + categories.FamRed + categories.ChA &lt; 0.0)</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">min(35.0, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
<field name="amount_python_compute">min(36.0, - (categories.PP + categories.PPRed + categories.FamRed + categories.ChA))</field>
</record>
<record id="hr_payroll_rules_parent_company_car" model="hr.salary.rule">
<field name="category_id" ref="hr_payroll_head_div_impos"/>
@@ -559,7 +547,6 @@ result = -contract.car_atn
ref('hr_payroll_rules_retained_net'),
ref('hr_salary_rule_commission_on_target'),
ref('hr_salary_rule_pp_reduction_employees'),
ref('hr_salary_rule_pp_reduction_low_salary'),
])]" name="rule_ids"/>
<field name="company_id" ref="base.main_company"/>
@@ -94,7 +94,7 @@ class HrContract(models.Model):
elif contract.transport_mode == 'public_transport':
contract.transport_employer_cost = contract.public_transport_reimbursed_amount
elif contract.transport_mode == 'others':
contract.transport_employer_cost == contract.others_reimbursed_amount
contract.transport_employer_cost = contract.others_reimbursed_amount
@api.depends('commission_on_target')
def _compute_warrants_cost(self):
@@ -176,6 +176,16 @@ class HrContract(models.Model):
contract.double_holidays = contract.wage * 0.92
contract.thirteen_month = contract.wage
@api.onchange('transport_mode')
def _onchange_transport_mode(self):
if self.transport_mode != 'company_car':
self.fuel_card = 0
self.company_car_total_depreciated_cost = 0
if self.transport_mode != 'others':
self.others_reimbursed_amount = 0
if self.transport_mode != 'public_transports':
self.public_transport_reimbursed_amount = 0
@api.onchange('mobile', 'mobile_plus')
def _onchange_mobile(self):
if self.mobile_plus and not self.mobile:
@@ -46,6 +46,13 @@ class HrContract(models.Model):
self.car_id = self.env['fleet.vehicle'].search([('driver_id', '=', self.employee_id.address_home_id.id)], limit=1)
return {'domain': {'car_id': self._get_available_cars_domain()}}
@api.onchange('transport_mode')
def _onchange_transport_mode(self):
super(HrContract, self)._onchange_transport_mode()
if self.transport_mode != 'company_car':
self.car_id = False
self.new_car_model_id = False
def _get_available_cars_domain(self):
return ['|', ('driver_id', '=', False), ('driver_id', '=', self.employee_id.address_home_id.id)]
@@ -165,6 +165,7 @@ class XmlDeclaration(models.TransientModel):
lines = self.env.cr.fetchall()
invoicelines_ids = [rec[0] for rec in lines]
invoicelines = self.env['account.invoice.line'].browse(invoicelines_ids)
for inv_line in invoicelines:
#Check type of transaction
@@ -190,6 +191,8 @@ class XmlDeclaration(models.TransientModel):
#comes from purchase
po_lines = self.env['purchase.order.line'].search([('invoice_lines', 'in', inv_line.id)], limit=1)
if po_lines:
if self._is_situation_triangular(company, po_line=po_lines):
continue
location = self.env['stock.location'].browse(po_lines.order_id._get_destination_location())
region_id = self.env['stock.warehouse'].get_regionid_from_locationid(location)
if region_id:
@@ -198,6 +201,8 @@ class XmlDeclaration(models.TransientModel):
#comes from sales
so_lines = self.env['sale.order.line'].search([('invoice_lines', 'in', inv_line.id)], limit=1)
if so_lines:
if self._is_situation_triangular(company, so_line=so_lines):
continue
saleorder = so_lines.order_id
if saleorder and saleorder.warehouse_id and saleorder.warehouse_id.region_id:
exreg = IntrastatRegion.browse(saleorder.warehouse_id.region_id.id).code
@@ -287,3 +292,24 @@ class XmlDeclaration(models.TransientModel):
dim = ET.SubElement(item, 'Dim')
dim.set('prop', prop)
dim.text = value
def _is_situation_triangular(self, company, po_line=False, so_line=False):
# Ignoring what is purchased and sold by us with a dropshipping route
# outside of our country, or completely within it
# https://www.nbb.be/doc/dq/f_pdf_ex/intra2017fr.pdf (§ 4.x)
dropship_pick_type = self.env.ref('stock_dropshipping.picking_type_dropship', raise_if_not_found=False)
if not dropship_pick_type:
return False
stock_move_domain = [('picking_type_id', '=', dropship_pick_type.id)]
if po_line:
stock_move_domain.append(('purchase_line_id', '=', po_line.id))
if so_line:
stock_move_domain.append(('procurement_id.sale_line_id', '=', so_line.id))
stock_move = self.env['stock.move'].search(stock_move_domain, limit=1)
return stock_move and (
(stock_move.partner_id.country_id.code != company.country_id.code and
stock_move.picking_partner_id.country_id.code != company.country_id.code) or
(stock_move.partner_id.country_id.code == company.country_id.code and
stock_move.picking_partner_id.country_id.code == company.country_id.code))
+9 -1
View File
@@ -166,6 +166,9 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
this.basic_composer.on('input_focused', this, this._onComposerFocused);
this.extended_composer.on('post_message', this, this._onPostMessage);
this.extended_composer.on('input_focused', this, this._onComposerFocused);
// manually call do_search to generate the initial domain and filter
// the messages in the default channel
self.searchview.do_search();
var defs = [];
defs.push(this._renderButtons());
@@ -908,7 +911,12 @@ var ChatAction = Widget.extend(ControlPanelMixin, {
contexts: [session.user_context],
});
this.domain = result.domain;
this._fetchAndRenderThread();
if (this.channel) {
// initially (when _onSearch is called manually), there is no
// channel set yet, so don't try to fetch and render the thread as
// this will be done as soon as the default channel is set
this._fetchAndRenderThread();
}
},
/**
* @private
@@ -21,9 +21,9 @@ BasicModel.include({
* @param {Object} record - an element from the localData
* @param {string} fieldName
* @return {Deferred<Object>} the deferred is resolved with the
* invalidPartnerIds
* invalidPartnerIds and partnerNames
*/
_setInvalidMany2ManyTagsEmail: function (record, fieldName) {
_fetchSpecialMany2ManyTagsEmail: function (record, fieldName) {
var self = this;
var localID = (record._changes && fieldName in record._changes) ?
record._changes[fieldName] :
@@ -37,14 +37,26 @@ BasicModel.include({
}
});
var def;
if (invalidPartnerIds) {
if (invalidPartnerIds.length) {
// remove invalid partners
var changes = {operation: 'DELETE', ids: _.pluck(invalidPartnerIds, 'id')};
def = this._applyX2ManyChange(record, fieldName, changes);
}
return $.when(def).then(function () {
return $.when({
invalidPartnerIds: _.pluck(invalidPartnerIds, 'res_id'),
list = self._applyX2ManyOperations(self.localData[localID]);
if (list.res_ids.length) {
def = self._rpc({
model: list.model,
method: 'name_get',
args: list.res_ids,
context: record.getContext({fieldName: fieldName}),
});
}
return $.when(def).then(function (names) {
return $.when({
invalidPartnerIds: _.pluck(invalidPartnerIds, 'res_id'),
partnerNames: _.object(names),
});
});
});
},
@@ -54,7 +66,7 @@ var FieldMany2ManyTagsEmail = M2MTags.extend({
fieldsToFetch: _.extend({}, M2MTags.prototype.fieldsToFetch, {
email: {type: 'char'},
}),
specialData: "_setInvalidMany2ManyTagsEmail",
specialData: "_fetchSpecialMany2ManyTagsEmail",
//--------------------------------------------------------------------------
// Private
@@ -132,14 +144,17 @@ var FieldMany2ManyTagsEmail = M2MTags.extend({
* Override for res.partner
* name_get is dynamic (based on context) while display_name is static
* (stored)
*
* @override
* @private
*/
get_render_data: function(ids){
this.dataset.cancel_read();
return this.dataset.name_get(ids).then(function (names) {
return _.map(names, function(name) {
return {id: name[0], display_name: name[1]};
});
_getRenderTagsContext: function () {
var result = this._super.apply(this, arguments);
var partnerNames = this.record.specialData[this.name].partnerNames;
_.each(result.elements, function (partner) {
partner.display_name = partnerNames[partner.id];
});
return result;
},
});
+1 -1
View File
@@ -44,7 +44,7 @@
</dd>
<dt>Assigned to</dt>
<dd class="mb8">
<img t-attf-src="/web/image#{activity.user_id[0] >= 0 ? ('/res.users/' + activity.user_id[0] + '/image_small') : ''}" height="18" width="18" class="img-circle mr4" t-att-title="activity.create_uid[1]"/>
<img t-attf-src="/web/image#{activity.user_id[0] >= 0 ? ('/res.users/' + activity.user_id[0] + '/image_small') : ''}" height="18" width="18" class="img-circle mr4" t-att-title="activity.user_id[1]"/>
<b><t t-esc="activity.user_id[1]"/></b>
<em>, due on </em><span t-attf-class="o_activity_color_#{activity.state}"><t t-esc="activity.date_deadline.format(date_format)"/></span>
</dd>
+7 -2
View File
@@ -1250,8 +1250,9 @@ QUnit.module('FieldMany2ManyTagsEmail', {
});
QUnit.test('fieldmany2many tags email', function (assert) {
assert.expect(11);
assert.expect(13);
var done = assert.async();
var nameGottenIds = [[12], [12, 14]];
this.data.partner.records[0].timmy = [12, 14];
@@ -1271,7 +1272,11 @@ QUnit.test('fieldmany2many tags email', function (assert) {
mode: 'edit',
},
mockRPC: function (route, args) {
if (args.method ==='read' && args.model === 'partner_type') {
if (route === "/web/dataset/call_kw/partner_type/name_get") {
assert.deepEqual(args.args, nameGottenIds.shift(),
"partner with email should be name_get'ed");
}
else if (args.method ==='read' && args.model === 'partner_type') {
assert.step(args.args[0]);
assert.deepEqual(args.args[1] , ['display_name', 'email'], "should read the email");
}
+8 -4
View File
@@ -1103,11 +1103,15 @@ class ProductCategory(models.Model):
class MailComposeMessage(models.TransientModel):
_inherit = 'mail.compose.message'
@api.multi
def mail_purchase_order_on_send(self):
if not self.filtered('subtype_id.internal'):
order = self.env['purchase.order'].browse(self._context['default_res_id'])
if order.state == 'draft':
order.state = 'sent'
@api.multi
def send_mail(self, auto_commit=False):
if self._context.get('default_model') == 'purchase.order' and self._context.get('default_res_id'):
if not self.filtered('subtype_id.internal'):
order = self.env['purchase.order'].browse([self._context['default_res_id']])
if order.state == 'draft':
order.state = 'sent'
self.mail_purchase_order_on_send()
return super(MailComposeMessage, self.with_context(mail_post_autofollow=True)).send_mail(auto_commit=auto_commit)
@@ -59,7 +59,7 @@
<field name="inherit_id" ref="account.invoice_supplier_form"/>
<field name="arch" type="xml">
<field name="reference" position="after" >
<field name="purchase_id" attrs="{'invisible': [('state', '=', 'purchase')]}" class="oe_edit_only"
<field name="purchase_id" attrs="{'readonly': [('state','not in',['draft'])], 'invisible': [('state', '=', 'purchase')]}" class="oe_edit_only"
options="{'no_create': True}" context="{'show_total_amount': True}"/>
</field>
<xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='company_id']" position="after">
+18
View File
@@ -849,6 +849,18 @@ class SaleOrderLine(models.Model):
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
self.filtered(
lambda r: r.state == 'sale' and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) != 0)._update_line_quantity(values)
# Prevent writing on a locked SO.
protected_fields = self._get_protected_fields()
if 'done' in self.mapped('order_id.state') and any(f in values.keys() for f in protected_fields):
fields = self.env['ir.model.fields'].search([
('name', 'in', protected_fields), ('model', '=', self._name)
])
raise UserError(
_('It is forbidden to modify the following fields in a locked order:\n%s')
% '\n'.join(fields.mapped('field_description'))
)
result = super(SaleOrderLine, self).write(values)
return result
@@ -1138,6 +1150,12 @@ class SaleOrderLine(models.Model):
return product[field_name] * uom_factor * cur_factor, currency_id.id
def _get_protected_fields(self):
return [
'product_id', 'name', 'price_unit', 'product_uom', 'product_uom_qty',
'tax_id', 'analytic_tag_ids'
]
@api.onchange('product_id', 'price_unit', 'product_uom', 'product_uom_qty', 'tax_id')
def _onchange_discount(self):
self.discount = 0.0
@@ -76,12 +76,14 @@ class SaleAdvancePaymentInv(models.TransientModel):
if self.amount <= 0.00:
raise UserError(_('The value of the down payment amount must be positive.'))
context = {'lang': order.partner_id.lang}
if self.advance_payment_method == 'percentage':
amount = order.amount_untaxed * self.amount / 100
name = _("Down payment of %s%%") % (self.amount,)
else:
amount = self.amount
name = _('Down Payment')
del context
taxes = self.product_id.taxes_id.filtered(lambda r: not order.company_id or r.company_id == order.company_id)
if order.fiscal_position_id and taxes:
tax_ids = order.fiscal_position_id.map_tax(taxes).ids
@@ -152,6 +154,7 @@ class SaleAdvancePaymentInv(models.TransientModel):
tax_ids = order.fiscal_position_id.map_tax(taxes).ids
else:
tax_ids = taxes.ids
context = {'lang': order.partner_id.lang}
so_line = sale_line_obj.create({
'name': _('Advance: %s') % (time.strftime('%m %Y'),),
'price_unit': amount,
@@ -163,6 +166,7 @@ class SaleAdvancePaymentInv(models.TransientModel):
'tax_id': [(6, 0, tax_ids)],
'is_downpayment': True,
})
del context
self._create_invoice(order, so_line, amount)
if self._context.get('open_invoices', False):
return sale_orders.action_view_invoice()
+1
View File
@@ -13,6 +13,7 @@ class PaymentTransaction(models.Model):
# YTI FIXME: The auto_join seems useless
sale_order_id = fields.Many2one('sale.order', string='Sales Order', auto_join=True)
so_state = fields.Selection('sale.order', string='Sale Order State', related='sale_order_id.state')
@api.model
def form_feedback(self, data, acquirer_name):
+16
View File
@@ -15,6 +15,22 @@ msgstr ""
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: stock
#: code:addons/stock/models/stock_move.py:230
#, python-format
msgid "\n"
"\n"
"%s --> Product UoM is %s (%s) - Move UoM is %s (%s)"
msgstr ""
#. module: stock
#: code:addons/stock/models/stock_move.py:231
#, python-format
msgid "\n"
"\n"
"Blocking: %s"
msgstr ""
#. module: stock
#: code:addons/stock/models/stock_move.py:308
#, python-format
+4 -2
View File
@@ -316,10 +316,12 @@ class StockMove(models.Model):
@api.constrains('product_uom')
def _check_uom(self):
moves_error = self.filtered(lambda move: move.product_id.uom_id.category_id.id != move.product_uom.category_id.id)
moves_error = self.filtered(lambda move: move.product_id.uom_id.category_id != move.product_uom.category_id)
if moves_error:
user_warning = _('You try to move a product using a UoM that is not compatible with the UoM of the product moved. Please use an UoM in the same UoM category.')
user_warning += '\n\nBlocking: %s' % ' ,'.join(moves_error.mapped('name'))
for move in moves_error:
user_warning += _('\n\n%s --> Product UoM is %s (%s) - Move UoM is %s (%s)') % (move.product_id.display_name, move.product_id.uom_id.name, move.product_id.uom_id.category_id.name, move.product_uom.name, move.product_uom.category_id.name)
user_warning += _('\n\nBlocking: %s') % ' ,'.join(moves_error.mapped('name'))
raise UserError(user_warning)
@api.model_cr
+3 -1
View File
@@ -455,6 +455,7 @@ class Warehouse(models.Model):
self.Routing(warehouse.lot_stock_id, warehouse.wh_pack_stock_loc_id, warehouse.pick_type_id),
self.Routing(warehouse.wh_pack_stock_loc_id, warehouse.wh_output_stock_loc_id, warehouse.pack_type_id),
self.Routing(warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id)],
'company_id': warehouse.company_id.id,
}) for warehouse in self)
def _get_reception_delivery_route_values(self, route_type):
@@ -495,7 +496,8 @@ class Warehouse(models.Model):
'product_categ_selectable': True,
'active': self.delivery_steps != 'ship_only' and self.reception_steps != 'one_step',
'company_id': self.company_id.id,
'sequence': 20}
'sequence': 20,
}
# Pull / Push tools
# ------------------------------------------------------------
+1
View File
@@ -99,6 +99,7 @@
<field name="product_id" type="row"/>
<field name="location_id" type="col"/>
<field name="quantity" type="measure"/>
<field name="inventory_value" type="measure"/>
</pivot>
</field>
</record>
+3 -1
View File
@@ -117,7 +117,9 @@ class ProductProduct(models.Model):
""" Changes the Standard Price of Product and creates an account move accordingly."""
AccountMove = self.env['account.move']
locations = self.env['stock.location'].search([('usage', '=', 'internal'), ('company_id', '=', self.env.user.company_id.id)])
quant_locs = self.env['stock.quant'].sudo().read_group([('product_id', 'in', self.ids)], ['location_id'], ['location_id'])
quant_loc_ids = [loc['location_id'][0] for loc in quant_locs]
locations = self.env['stock.location'].search([('usage', '=', 'internal'), ('company_id', '=', self.env.user.company_id.id), ('id', 'in', quant_loc_ids)])
product_accounts = {product.id: product.product_tmpl_id.get_product_accounts() for product in self}
@@ -97,11 +97,13 @@ var x2ManyCommands = {
// (0, virtualID, {values})
CREATE: 0,
create: function (virtualID, values) {
delete values.id;
return [x2ManyCommands.CREATE, virtualID || false, values];
},
// (1, id, {values})
UPDATE: 1,
update: function (id, values) {
delete values.id;
return [x2ManyCommands.UPDATE, id, values];
},
// (2, id[, _])
@@ -2669,7 +2671,6 @@ var BasicModel = AbstractModel.extend({
_.each(relRecordUpdated, function (relRecord) {
var changes = self._generateChanges(relRecord, options);
if (!_.isEmpty(changes)) {
delete changes.id;
var command = x2ManyCommands.update(relRecord.res_id, changes);
commands[fieldName].push(command);
}
@@ -2689,7 +2690,6 @@ var BasicModel = AbstractModel.extend({
relRecord = _.findWhere(relRecordUpdated, {res_id: list.res_ids[i]});
changes = relRecord ? this._generateChanges(relRecord, options) : {};
if (!_.isEmpty(changes)) {
delete changes.id;
command = x2ManyCommands.update(relRecord.res_id, changes);
didChange = true;
} else {
@@ -2700,7 +2700,7 @@ var BasicModel = AbstractModel.extend({
// this is a new id (maybe existing in DB, but new in JS)
relRecord = _.findWhere(relRecordAdded, {res_id: list.res_ids[i]});
changes = this._generateChanges(relRecord, options);
if ('id' in changes) {
if (changes.id) {
// the subrecord already exists in db
delete changes.id;
if (this.isNew(record.id)) {
+1 -1
View File
@@ -835,7 +835,7 @@
<t t-foreach="elements" t-as="el">
<t t-set="color" t-value="el[colorField] || 0"/>
<span t-attf-class="badge dropdown o_tag_color_#{color}" t-att-data-color="color" t-att-data-index="el_index" t-att-data-id="el.id">
<span class="o_badge_text"><t t-raw="el.display_name"/></span>
<span class="o_badge_text"><t t-esc="el.display_name"/></span>
<span t-if="!readonly" class="fa fa-times o_delete"/>
</span>
</t>
@@ -5435,6 +5435,37 @@ QUnit.module('relational_fields', {
form.destroy();
});
QUnit.test('id field in one2many in a new record', function (assert) {
assert.expect(1);
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<field name="turtles">' +
'<tree editable="bottom">' +
'<field name="id" invisible="1"/>' +
'<field name="turtle_foo"/>' +
'</tree>' +
'</field>' +
'</form>',
mockRPC: function (route, args) {
if (args.method === 'create') {
assert.deepEqual(args.args[0].turtles,
[[0, false, {turtle_foo: "cat"}]],
'should send proper commands');
}
return this._super.apply(this, arguments);
},
});
form.$('td.o_field_x2many_list_row_add a').click();
form.$('td input[name="turtle_foo"]').val('cat').trigger('input');
form.$buttons.find('.o_form_button_save').click();
form.destroy();
});
QUnit.test('sub form view with a required field', function (assert) {
assert.expect(2);
this.data.partner.fields.foo.required = true;
+11 -4
View File
@@ -437,7 +437,7 @@ class Post(models.Model):
elif post.parent_id and not post.can_answer:
raise KarmaError('Not enough karma to answer to a question')
if not post.parent_id and not post.can_post:
post.state = 'pending'
post.sudo().state = 'pending'
# add karma for posting new questions
if not post.parent_id and post.state == 'active':
@@ -466,11 +466,18 @@ class Post(models.Model):
@api.multi
def write(self, vals):
trusted_keys = ['active', 'is_correct', 'tag_ids'] # fields where security is checked manually
if 'content' in vals:
vals['content'] = self._update_content(vals['content'], self.forum_id.id)
if 'state' in vals:
if vals['state'] in ['active', 'close'] and any(not post.can_close for post in self):
raise KarmaError('Not enough karma to close or reopen a post.')
if vals['state'] in ['active', 'close']:
if any(not post.can_close for post in self):
raise KarmaError('Not enough karma to close or reopen a post.')
trusted_keys += ['state', 'closed_uid', 'closed_date', 'closed_reason_id']
elif vals['state'] == 'flagged':
if any(not post.can_flag for post in self):
raise KarmaError('Not enough karma to flag a post.')
trusted_keys += ['state', 'flag_user_id']
if 'active' in vals:
if any(not post.can_unlink for post in self):
raise KarmaError('Not enough karma to delete or reactivate a post')
@@ -487,7 +494,7 @@ class Post(models.Model):
tag_ids = set(tag.get('id') for tag in self.resolve_2many_commands('tag_ids', vals['tag_ids']))
if any(set(post.tag_ids) != tag_ids for post in self) and any(self.env.user.karma < post.forum_id.karma_edit_retag for post in self):
raise KarmaError(_('Not enough karma to retag.'))
if any(key not in ['state', 'active', 'is_correct', 'closed_uid', 'closed_date', 'closed_reason_id', 'tag_ids'] for key in vals) and any(not post.can_edit for post in self):
if any(key not in trusted_keys for key in vals) and any(not post.can_edit for post in self):
raise KarmaError('Not enough karma to edit a post.')
res = super(Post, self).write(vals)
+3 -3
View File
@@ -5,9 +5,9 @@
<div>
<div t-if="'karma' in fields" class='css_editable_mode_hidden'>
<div t-if="options.get('UserBio')">
<span t-field="object.company_id.name" class="o_forum_tooltip_line"/><br/>
<a t-att-href="object.company_id.website" t-if="object.company_id.website">
<span t-field="object.company_id.website" class="o_forum_tooltip_line"/>
<span t-field="object.company_name" class="o_forum_tooltip_line"/><br/>
<a t-att-href="object.website" t-if="object.website">
<span t-field="object.website" class="o_forum_tooltip_line"/>
</a>
</div>
<b><t t-esc="object.karma"/></b>
+2 -1
View File
@@ -259,7 +259,8 @@ class SaleOrderOption(models.Model):
order_line = order.order_line.filtered(lambda line: line.product_id == self.product_id)
if order_line:
order_line[0].product_uom_qty += 1
order_line = order_line[0]
order_line.product_uom_qty += 1
else:
vals = {
'price_unit': self.price_unit,