[PATCH] Upstream patch - 01092021

This commit is contained in:
Parthiv Patel committed 2021-09-01 04:49:59 +00:00
1 parent ee040cbd30
commit 167ec0116a
55 files changed
+621 -169

No files matched your search

+2 -1
View File
@@ -325,7 +325,8 @@ class AccountJournal(models.Model):
@api.constrains('active')
def _check_auto_post_draft_entries(self):
for journal in self:
# constraint should be tested just after archiving a journal, but shouldn't be raised when unarchiving a journal containing draft entries
for journal in self.filtered(lambda j: not j.active):
pending_moves = self.env['account.move'].search([
('journal_id', '=', journal.id),
('state', '=', 'draft')
+2 -2
View File
@@ -21,8 +21,8 @@ class SequenceMixin(models.AbstractModel):
_sequence_field = "name"
_sequence_date_field = "date"
_sequence_index = False
_sequence_monthly_regex = r'^(?P<prefix1>.*?)(?P<year>((?<=\D)|(?<=^))((20|21)\d{2}|(\d{2}(?=\D))))(?P<prefix2>\D*?)(?P<month>(0[1-9]|1[0-2]))(?P<prefix3>\D+?)(?P<seq>\d*)(?P<suffix>\D*?)$'
_sequence_yearly_regex = r'^(?P<prefix1>.*?)(?P<year>((?<=\D)|(?<=^))((20|21)?\d{2}))(?P<prefix2>\D+?)(?P<seq>\d*)(?P<suffix>\D*?)$'
_sequence_monthly_regex = r'^(?P<prefix1>.*?)(?P<year>((?<=\D)|(?<=^))((19|20|21)\d{2}|(\d{2}(?=\D))))(?P<prefix2>\D*?)(?P<month>(0[1-9]|1[0-2]))(?P<prefix3>\D+?)(?P<seq>\d*)(?P<suffix>\D*?)$'
_sequence_yearly_regex = r'^(?P<prefix1>.*?)(?P<year>((?<=\D)|(?<=^))((19|20|21)?\d{2}))(?P<prefix2>\D+?)(?P<seq>\d*)(?P<suffix>\D*?)$'
_sequence_fixed_regex = r'^(?P<prefix1>.*?)(?P<seq>\d{0,9})(?P<suffix>\D*?)$'
sequence_prefix = fields.Char(compute='_compute_split_sequence', store=True)
+52 -32
View File
@@ -28,43 +28,63 @@ flectra.define('account.tax_group', function (require) {
* @param {Id} taxGroupId
* @param {Float} deltaAmount
*/
_changeTaxValueByTaxGroup: function (taxGroupId, deltaAmount) {
var self = this;
// Search for the first tax line with the same tax group and modify its value
var line_id = self.record.data.line_ids.data.find(elem => elem.data.tax_group_id && elem.data.tax_group_id.data.id === taxGroupId);
_changeTaxValueByTaxGroup: function (taxGroupId, deltaAmount) {
const self = this;
var debitAmount = 0;
var creditAmount = 0;
var amount_currency = 0;
if (line_id.data.currency_id) { // If multi currency enable
if (this.record.data.move_type === "in_invoice") {
amount_currency = line_id.data.amount_currency - deltaAmount;
// Search for the first tax line with the same tax group and modify its value
function applyChange(line_id){
let debitAmount = 0;
let creditAmount = 0;
let amount_currency = 0;
if (line_id.data.currency_id) { // If multi currency enable
if (self.record.data.move_type === "in_invoice") {
amount_currency = line_id.data.amount_currency - deltaAmount;
} else {
amount_currency = line_id.data.amount_currency + deltaAmount;
}
} else {
amount_currency = line_id.data.amount_currency + deltaAmount;
}
} else {
var balance = line_id.data.price_subtotal;
balance -= deltaAmount;
if (this.record.data.move_type === "in_invoice") { // For vendor bill
if (balance > 0) {
debitAmount = balance;
} else if (balance < 0) {
creditAmount = -balance;
}
} else { // For refund
if (balance > 0) {
creditAmount = balance;
} else if (balance < 0) {
debitAmount = -balance;
let balance = line_id.data.price_subtotal;
balance -= deltaAmount;
if (self.record.data.move_type === "in_invoice") { // For vendor bill
if (balance > 0) {
debitAmount = balance;
} else if (balance < 0) {
creditAmount = -balance;
}
} else { // For refund
if (balance > 0) {
creditAmount = balance;
} else if (balance < 0) {
debitAmount = -balance;
}
}
}
// Trigger ORM
self.trigger_up('field_changed', {
dataPointID: self.record.id,
changes: { line_ids: { operation: "UPDATE", id: line_id.id, data: { amount_currency: amount_currency, debit: debitAmount, credit: creditAmount } } }, // account.move change
initialEvent: { dataPointID: line_id.id, changes: { amount_currency: amount_currency, debit: debitAmount, credit: creditAmount }, }, // account.move.line change
});
}
let line_id = self.record.data.line_ids.data.find(elem => elem.data.tax_group_id && elem.data.tax_group_id.data.id === taxGroupId);
if (line_id){
applyChange(line_id);
} else {
const {limit, id, count} = self.record.data.line_ids;
let offset = count - limit;
self.trigger_up('load', {
id,
limit,
offset,
on_success: value => {
line_id = value.data.find(elem => elem.data.tax_group_id && elem.data.tax_group_id.data.id === taxGroupId);
applyChange(line_id);
},
});
}
// Trigger ORM
self.trigger_up('field_changed', {
dataPointID: self.record.id,
changes: { line_ids: { operation: "UPDATE", id: line_id.id, data: { amount_currency: amount_currency, debit: debitAmount, credit: creditAmount } } }, // account.move change
initialEvent: { dataPointID: line_id.id, changes: { amount_currency: amount_currency, debit: debitAmount, credit: creditAmount }, }, // account.move.line change
});
},
/**
+1 -1
View File
@@ -17,7 +17,7 @@ class FleetVehicle(models.Model):
return
for vehicle in self:
vehicle.account_move_ids = self.env['account.move.line'].search([('vehicle_id', '=', self.id), ('move_id.state', '!=', 'cancel')]).move_id
vehicle.account_move_ids = self.env['account.move.line'].search([('vehicle_id', '=', vehicle.id), ('move_id.state', '!=', 'cancel')]).move_id
vehicle.bill_count = len(vehicle.account_move_ids)
def action_view_bills(self):
+3 -2
View File
@@ -18,7 +18,7 @@ class SaleOrder(models.Model):
@api.depends('order_line')
def _compute_is_service_products(self):
for so in self:
so.is_all_service = all(line.product_id.type == 'service' for line in so.order_line)
so.is_all_service = all(line.product_id.type == 'service' for line in so.order_line.filtered(lambda x: not x.display_type))
def _compute_amount_total_without_delivery(self):
self.ensure_one()
@@ -30,8 +30,9 @@ class SaleOrder(models.Model):
for order in self:
order.delivery_set = any(line.is_delivery for line in order.order_line)
@api.onchange('order_line', 'partner_id')
@api.onchange('order_line', 'partner_id', 'partner_shipping_id')
def onchange_order_line(self):
self.ensure_one()
delivery_line = self.order_line.filtered('is_delivery')
if delivery_line:
self.recompute_delivery_price = True
+1
View File
@@ -41,6 +41,7 @@ class StockMoveLine(models.Model):
unit_price = move_line.product_id.list_price
qty = move_line.product_uom_id._compute_quantity(move_line.qty_done, move_line.product_id.uom_id)
move_line.sale_price = unit_price * qty
super(StockMoveLine, self)._compute_sale_price()
def _get_aggregated_product_quantities(self, **kwargs):
"""Returns dictionary of products and corresponding values of interest + hs_code
@@ -38,7 +38,7 @@
<t t-set="checked_in" t-value="widget.employee.attendance_state=='checked_in'"/>
<t t-if="widget.employee">
<div class="o_hr_attendance_user_badge o_home_menu_background">
<img class="img rounded-circle" t-attf-src="/web/image?model=hr.employee&amp;field=image_128&amp;id=#{widget.employee.id}" t-att-title="widget.employee.name" t-att-alt="widget.employee.name"/>
<img class="img rounded-circle" t-attf-src="/web/image?model=hr.employee.public&amp;field=image_128&amp;id=#{widget.employee.id}" t-att-title="widget.employee.name" t-att-alt="widget.employee.name"/>
</div>
<h1 class="mb8"><t t-esc="widget.employee.name"/></h1>
<h3 class="mt8 mb24"><t t-if="!checked_in">Welcome!</t><t t-else="">Want to check out?</t></h3>
@@ -66,7 +66,7 @@
</div>
<t t-if="widget.employee_id">
<div class="o_hr_attendance_user_badge o_home_menu_background">
<img class="img rounded-circle" t-attf-src="/web/image?model=hr.employee&amp;field=image_128&amp;id=#{widget.employee_id}" t-att-title="widget.employee_name" t-att-alt="widget.employee_name"/>
<img class="img rounded-circle" t-attf-src="/web/image?model=hr.employee.public&amp;field=image_128&amp;id=#{widget.employee_id}" t-att-title="widget.employee_name" t-att-alt="widget.employee_name"/>
</div>
<h1 class="mb8"><t t-esc="widget.employee_name"/></h1>
<h3 class="mt8 mb24"><t t-if="!checked_in">Welcome!</t><t t-else="">Want to check out?</t></h3>
@@ -109,7 +109,7 @@
<div class="o_hr_attendance_kiosk_mode">
<t t-if="widget.attendance">
<div class="o_hr_attendance_user_badge o_home_menu_background">
<img class="img rounded-circle" t-attf-src="/web/image?model=hr.employee&amp;field=image_128&amp;id=#{widget.attendance.employee_id[0]}" t-att-title="widget.employee_name" t-att-alt="widget.employee_name"/>
<img class="img rounded-circle" t-attf-src="/web/image?model=hr.employee.public&amp;field=image_128&amp;id=#{widget.attendance.employee_id[0]}" t-att-title="widget.employee_name" t-att-alt="widget.employee_name"/>
</div>
<t t-if="widget.attendance.check_out">
<h1 class="mb0">Goodbye <t t-esc="widget.employee_name"/>!</h1>
@@ -137,6 +137,12 @@
<field name="domain_force">['|', ('holiday_status_id.company_id', '=', False), ('holiday_status_id.company_id', 'in', company_ids)]</field>
</record>
<record id="hr_leave_allocation_rule_multicompany" model="ir.rule">
<field name="name">Leave Allocations: multi company global rule</field>
<field name="model_id" ref="model_hr_leave_allocation"/>
<field name="domain_force">['|', ('holiday_status_id.company_id', '=', False), ('holiday_status_id.company_id', 'in', company_ids)]</field>
</record>
<record id="hr_leave_allocation_rule_employee" model="ir.rule">
<field name="name">Allocations: employee: read own</field>
<field name="model_id" ref="model_hr_leave_allocation"/>
@@ -420,6 +420,9 @@ class Applicant(models.Model):
# want the gateway user to be responsible if no other responsible is
# found.
self = self.with_context(default_user_id=False)
stage = False
if custom_values and 'job_id' in custom_values:
stage = self.env['hr.job'].browse(custom_values['job_id'])._get_first_stage()
val = msg.get('from').split('<')[0]
defaults = {
'name': msg.get('subject') or _("No Subject"),
@@ -429,6 +432,8 @@ class Applicant(models.Model):
}
if msg.get('priority'):
defaults['priority'] = msg.get('priority')
if stage and stage.id:
defaults['stage_id'] = stage.id
if custom_values:
defaults.update(custom_values)
return super(Applicant, self).message_new(msg, custom_values=defaults)
+2 -1
View File
@@ -97,13 +97,14 @@ class IapTransaction(object):
self.credit = None
def iap_authorize(env, key, account_token, credit, dbuuid=False, description=None, credit_template=None):
def iap_authorize(env, key, account_token, credit, dbuuid=False, description=None, credit_template=None, ttl=4320):
endpoint = iap_get_endpoint(env)
params = {
'account_token': account_token,
'credit': credit,
'key': key,
'description': description,
'ttl': ttl
}
if dbuuid:
params.update({'dbuuid': dbuuid})
+3 -3
View File
@@ -22,11 +22,11 @@
<field name="inherit_id" ref="account.view_tax_form"/>
<field name="arch" type="xml">
<field position="after" name="price_include">
<field name="tax_discount"/>
<field name="tax_discount" attrs="{'invisible': [('country_code', '!=', 'BR')]}"/>
</field>
<field position="after" name="tax_discount">
<field name="base_reduction" widget="monetary"/>
<field name="amount_mva" widget="monetary"/>
<field name="base_reduction" widget="monetary" attrs="{'invisible': [('country_code', '!=', 'BR')]}"/>
<field name="amount_mva" widget="monetary" attrs="{'invisible': [('country_code', '!=', 'BR')]}"/>
</field>
</field>
</record>
+9 -8
View File
@@ -54,12 +54,13 @@ class ResPartner(models.Model):
return super().create(values)
def write(self, values):
for record in self:
vat_values = {
'vat': values.get('vat', record.vat),
'l10n_latam_identification_type_id': values.get(
'l10n_latam_identification_type_id', record.l10n_latam_identification_type_id.id),
'country_id': values.get('country_id', record.country_id.id)
}
values['vat'] = self._format_vat_cl(vat_values)
if 'vat' in values or 'l10n_latam_identification_type_id' in values:
for record in self:
vat_values = {
'vat': values.get('vat', record.vat),
'l10n_latam_identification_type_id': values.get(
'l10n_latam_identification_type_id', record.l10n_latam_identification_type_id.id),
'country_id': values.get('country_id', record.country_id.id)
}
values['vat'] = self._format_vat_cl(vat_values)
return super().write(values)
+1 -1
View File
@@ -7,7 +7,7 @@
<field name="inherit_id" ref="account.view_tax_form"/>
<field name="arch" type="xml">
<field name="name" position="after">
<field name="l10n_cl_sii_code" options="{'format': false}"/>
<field name="l10n_cl_sii_code" options="{'format': false}" attrs="{'invisible': [('country_code', '!=', 'CL')]}"/>
</field>
</field>
</record>
+1 -1
View File
@@ -6,7 +6,7 @@
<field name="inherit_id" ref="account.view_tax_form"/>
<field name="arch" type="xml">
<field name="include_base_amount" position="after">
<field name="l10n_de_datev_code"/>
<field name="l10n_de_datev_code" attrs="{'invisible': [('country_code', '!=', 'DE')]}"/>
</field>
</field>
</record>
@@ -9,8 +9,11 @@ class AccountBankStatement(models.Model):
_inherit = 'account.bank.statement'
def unlink(self):
for statement in self.filtered(lambda s: s.company_id._is_accounting_unalterable() and s.journal_id.pos_payment_method_ids):
raise UserError(_('You cannot modify anything on a bank statement (name: %s) that was created by point of sale operations.') % (statement.name,))
for statement in self:
if not statement.company_id._is_accounting_unalterable() or not statement.s.journal_id.pos_payment_method_ids:
continue
if statement.state != 'open':
raise UserError(_('You cannot modify anything on a bank statement (name: %s) that was created by point of sale operations.') % (statement.name))
return super(AccountBankStatement, self).unlink()
+1 -1
View File
@@ -6,7 +6,7 @@
<field name="inherit_id" ref="account.view_tax_form"/>
<field name="arch" type="xml">
<field name="include_base_amount" position="after">
<field name="l10n_in_reverse_charge" attrs="{'invisible':[('amount_type','=', 'group')]}"/>
<field name="l10n_in_reverse_charge" attrs="{'invisible':['|', ('amount_type','=', 'group'), ('country_code', '!=', 'IN')]}"/>
</field>
</field>
</record>
+1 -1
View File
@@ -168,7 +168,7 @@
<xpath expr="//page[@name='other_info']" position="after">
<page string="Electronic Invoicing"
name="electronic_invoicing"
attrs="{'invisible': [('move_type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund'))]}">
attrs="{'invisible': ['|', ('move_type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund')), ('country_code', '!=', 'IT')]}">
<group>
<group>
<field name="l10n_it_stamp_duty"/>
@@ -806,33 +806,33 @@ chart5911,5911,Utilidades no distribuidas - Utilidades acumuladas,account.data_a
chart5912,5912,Utilidades no distribuidas - Ingresos de años anteriores,account.data_account_type_equity,pe_chart_template,False
chart5921,5921,Pérdidas acumuladas - Pérdidas acumuladas,account.data_account_type_equity,pe_chart_template,False
chart5922,5922,Pérdidas acumuladas - Gastos de años anteriores,account.data_account_type_equity,pe_chart_template,False
chart6011,6011,Mercaderías - Mercaderías,account.data_account_type_current_assets,pe_chart_template,False
chart602,602,Materias primas,account.data_account_type_current_assets,pe_chart_template,False
chart6031,6031,"Materiales auxiliares, suministros y repuestos - Materiales auxiliares",account.data_account_type_current_assets,pe_chart_template,False
chart6032,6032,"Materiales auxiliares, suministros y repuestos - Suministros",account.data_account_type_current_assets,pe_chart_template,False
chart6033,6033,"Materiales auxiliares, suministros y repuestos - Repuestos",account.data_account_type_current_assets,pe_chart_template,False
chart6041,6041,Envases y embalajes - Envases,account.data_account_type_current_assets,pe_chart_template,False
chart6042,6042,Envases y embalajes - Embalajes,account.data_account_type_current_assets,pe_chart_template,False
chart60911,60911,Costos vinculados con las compras - Costos vinculados con las compras de mercaderías - Transporte,account.data_account_type_current_assets,pe_chart_template,False
chart60912,60912,Costos vinculados con las compras - Costos vinculados con las compras de mercaderías - Seguros,account.data_account_type_current_assets,pe_chart_template,False
chart60913,60913,Costos vinculados con las compras - Costos vinculados con las compras de mercaderías - Derechos aduaneros,account.data_account_type_current_assets,pe_chart_template,False
chart60914,60914,Costos vinculados con las compras - Costos vinculados con las compras de mercaderías - Comisiones,account.data_account_type_current_assets,pe_chart_template,False
chart60919,60919,Costos vinculados con las compras - Costos vinculados con las compras de mercaderías - Otros costos,account.data_account_type_current_assets,pe_chart_template,False
chart60921,60921,Costos vinculados con las compras - Costos vinculados con las compras de materias primas - Transporte,account.data_account_type_current_assets,pe_chart_template,False
chart60922,60922,Costos vinculados con las compras - Costos vinculados con las compras de materias primas - Seguros,account.data_account_type_current_assets,pe_chart_template,False
chart60923,60923,Costos vinculados con las compras - Costos vinculados con las compras de materias primas - Derechos aduaneros,account.data_account_type_current_assets,pe_chart_template,False
chart60924,60924,Costos vinculados con las compras - Costos vinculados con las compras de materias primas - Comisiones,account.data_account_type_current_assets,pe_chart_template,False
chart60925,60925,Costos vinculados con las compras - Costos vinculados con las compras de materias primas - Otros costos,account.data_account_type_current_assets,pe_chart_template,False
chart60931,60931,"Costos vinculados con las compras - Costos vinculados con las compras de materiales, suministros y repuestos - Transporte",account.data_account_type_current_assets,pe_chart_template,False
chart60932,60932,"Costos vinculados con las compras - Costos vinculados con las compras de materiales, suministros y repuestos - Seguros",account.data_account_type_current_assets,pe_chart_template,False
chart60933,60933,"Costos vinculados con las compras - Costos vinculados con las compras de materiales, suministros y repuestos - Derechos aduaneros",account.data_account_type_current_assets,pe_chart_template,False
chart60934,60934,"Costos vinculados con las compras - Costos vinculados con las compras de materiales, suministros y repuestos - Comisiones",account.data_account_type_current_assets,pe_chart_template,False
chart60935,60935,"Costos vinculados con las compras - Costos vinculados con las compras de materiales, suministros y repuestos - Otros costos",account.data_account_type_current_assets,pe_chart_template,False
chart60941,60941,Costos vinculados con las compras - Costos vinculados con las compras de envases y embalajes - Transporte,account.data_account_type_current_assets,pe_chart_template,False
chart60942,60942,Costos vinculados con las compras - Costos vinculados con las compras de envases y embalajes - Seguros,account.data_account_type_current_assets,pe_chart_template,False
chart60943,60943,Costos vinculados con las compras - Costos vinculados con las compras de envases y embalajes - Derechos aduaneros,account.data_account_type_current_assets,pe_chart_template,False
chart60944,60944,Costos vinculados con las compras - Costos vinculados con las compras de envases y embalajes - Comisiones,account.data_account_type_current_assets,pe_chart_template,False
chart60945,60945,Costos vinculados con las compras - Costos vinculados con las compras de envases y embalajes - Otros costos,account.data_account_type_current_assets,pe_chart_template,False
chart6011,6011,Mercaderías - Mercaderías,account.data_account_type_expenses,pe_chart_template,False
chart602,602,Materias primas,account.data_account_type_expenses,pe_chart_template,False
chart6031,6031,"Materiales auxiliares, suministros y repuestos - Materiales auxiliares",account.data_account_type_expenses,pe_chart_template,False
chart6032,6032,"Materiales auxiliares, suministros y repuestos - Suministros",account.data_account_type_expenses,pe_chart_template,False
chart6033,6033,"Materiales auxiliares, suministros y repuestos - Repuestos",account.data_account_type_expenses,pe_chart_template,False
chart6041,6041,Envases y embalajes - Envases,account.data_account_type_expenses,pe_chart_template,False
chart6042,6042,Envases y embalajes - Embalajes,account.data_account_type_expenses,pe_chart_template,False
chart60911,60911,Costos vinculados con las compras - Costos vinculados con las compras de mercaderías - Transporte,account.data_account_type_expenses,pe_chart_template,False
chart60912,60912,Costos vinculados con las compras - Costos vinculados con las compras de mercaderías - Seguros,account.data_account_type_expenses,pe_chart_template,False
chart60913,60913,Costos vinculados con las compras - Costos vinculados con las compras de mercaderías - Derechos aduaneros,account.data_account_type_expenses,pe_chart_template,False
chart60914,60914,Costos vinculados con las compras - Costos vinculados con las compras de mercaderías - Comisiones,account.data_account_type_expenses,pe_chart_template,False
chart60919,60919,Costos vinculados con las compras - Costos vinculados con las compras de mercaderías - Otros costos,account.data_account_type_expenses,pe_chart_template,False
chart60921,60921,Costos vinculados con las compras - Costos vinculados con las compras de materias primas - Transporte,account.data_account_type_expenses,pe_chart_template,False
chart60922,60922,Costos vinculados con las compras - Costos vinculados con las compras de materias primas - Seguros,account.data_account_type_expenses,pe_chart_template,False
chart60923,60923,Costos vinculados con las compras - Costos vinculados con las compras de materias primas - Derechos aduaneros,account.data_account_type_expenses,pe_chart_template,False
chart60924,60924,Costos vinculados con las compras - Costos vinculados con las compras de materias primas - Comisiones,account.data_account_type_expenses,pe_chart_template,False
chart60925,60925,Costos vinculados con las compras - Costos vinculados con las compras de materias primas - Otros costos,account.data_account_type_expenses,pe_chart_template,False
chart60931,60931,"Costos vinculados con las compras - Costos vinculados con las compras de materiales, suministros y repuestos - Transporte",account.data_account_type_expenses,pe_chart_template,False
chart60932,60932,"Costos vinculados con las compras - Costos vinculados con las compras de materiales, suministros y repuestos - Seguros",account.data_account_type_expenses,pe_chart_template,False
chart60933,60933,"Costos vinculados con las compras - Costos vinculados con las compras de materiales, suministros y repuestos - Derechos aduaneros",account.data_account_type_expenses,pe_chart_template,False
chart60934,60934,"Costos vinculados con las compras - Costos vinculados con las compras de materiales, suministros y repuestos - Comisiones",account.data_account_type_expenses,pe_chart_template,False
chart60935,60935,"Costos vinculados con las compras - Costos vinculados con las compras de materiales, suministros y repuestos - Otros costos",account.data_account_type_expenses,pe_chart_template,False
chart60941,60941,Costos vinculados con las compras - Costos vinculados con las compras de envases y embalajes - Transporte,account.data_account_type_expenses,pe_chart_template,False
chart60942,60942,Costos vinculados con las compras - Costos vinculados con las compras de envases y embalajes - Seguros,account.data_account_type_expenses,pe_chart_template,False
chart60943,60943,Costos vinculados con las compras - Costos vinculados con las compras de envases y embalajes - Derechos aduaneros,account.data_account_type_expenses,pe_chart_template,False
chart60944,60944,Costos vinculados con las compras - Costos vinculados con las compras de envases y embalajes - Comisiones,account.data_account_type_expenses,pe_chart_template,False
chart60945,60945,Costos vinculados con las compras - Costos vinculados con las compras de envases y embalajes - Otros costos,account.data_account_type_expenses,pe_chart_template,False
chart6111,6111,Mercaderías - Mercaderías,account.data_account_type_expenses,pe_chart_template,False
chart6121,6121,Materias primas - Materias primas,account.data_account_type_expenses,pe_chart_template,False
chart6131,6131,"Materiales auxiliares, suministros y repuestos - Materiales auxiliares",account.data_account_type_expenses,pe_chart_template,False
1 id code name user_type_id:id chart_template_id:id reconcile
806 chart5912 5912 Utilidades no distribuidas - Ingresos de años anteriores account.data_account_type_equity pe_chart_template False
807 chart5921 5921 Pérdidas acumuladas - Pérdidas acumuladas account.data_account_type_equity pe_chart_template False
808 chart5922 5922 Pérdidas acumuladas - Gastos de años anteriores account.data_account_type_equity pe_chart_template False
809 chart6011 6011 Mercaderías - Mercaderías account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
810 chart602 602 Materias primas account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
811 chart6031 6031 Materiales auxiliares, suministros y repuestos - Materiales auxiliares account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
812 chart6032 6032 Materiales auxiliares, suministros y repuestos - Suministros account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
813 chart6033 6033 Materiales auxiliares, suministros y repuestos - Repuestos account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
814 chart6041 6041 Envases y embalajes - Envases account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
815 chart6042 6042 Envases y embalajes - Embalajes account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
816 chart60911 60911 Costos vinculados con las compras - Costos vinculados con las compras de mercaderías - Transporte account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
817 chart60912 60912 Costos vinculados con las compras - Costos vinculados con las compras de mercaderías - Seguros account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
818 chart60913 60913 Costos vinculados con las compras - Costos vinculados con las compras de mercaderías - Derechos aduaneros account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
819 chart60914 60914 Costos vinculados con las compras - Costos vinculados con las compras de mercaderías - Comisiones account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
820 chart60919 60919 Costos vinculados con las compras - Costos vinculados con las compras de mercaderías - Otros costos account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
821 chart60921 60921 Costos vinculados con las compras - Costos vinculados con las compras de materias primas - Transporte account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
822 chart60922 60922 Costos vinculados con las compras - Costos vinculados con las compras de materias primas - Seguros account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
823 chart60923 60923 Costos vinculados con las compras - Costos vinculados con las compras de materias primas - Derechos aduaneros account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
824 chart60924 60924 Costos vinculados con las compras - Costos vinculados con las compras de materias primas - Comisiones account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
825 chart60925 60925 Costos vinculados con las compras - Costos vinculados con las compras de materias primas - Otros costos account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
826 chart60931 60931 Costos vinculados con las compras - Costos vinculados con las compras de materiales, suministros y repuestos - Transporte account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
827 chart60932 60932 Costos vinculados con las compras - Costos vinculados con las compras de materiales, suministros y repuestos - Seguros account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
828 chart60933 60933 Costos vinculados con las compras - Costos vinculados con las compras de materiales, suministros y repuestos - Derechos aduaneros account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
829 chart60934 60934 Costos vinculados con las compras - Costos vinculados con las compras de materiales, suministros y repuestos - Comisiones account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
830 chart60935 60935 Costos vinculados con las compras - Costos vinculados con las compras de materiales, suministros y repuestos - Otros costos account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
831 chart60941 60941 Costos vinculados con las compras - Costos vinculados con las compras de envases y embalajes - Transporte account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
832 chart60942 60942 Costos vinculados con las compras - Costos vinculados con las compras de envases y embalajes - Seguros account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
833 chart60943 60943 Costos vinculados con las compras - Costos vinculados con las compras de envases y embalajes - Derechos aduaneros account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
834 chart60944 60944 Costos vinculados con las compras - Costos vinculados con las compras de envases y embalajes - Comisiones account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
835 chart60945 60945 Costos vinculados con las compras - Costos vinculados con las compras de envases y embalajes - Otros costos account.data_account_type_current_assets account.data_account_type_expenses pe_chart_template False
836 chart6111 6111 Mercaderías - Mercaderías account.data_account_type_expenses pe_chart_template False
837 chart6121 6121 Materias primas - Materias primas account.data_account_type_expenses pe_chart_template False
838 chart6131 6131 Materiales auxiliares, suministros y repuestos - Materiales auxiliares account.data_account_type_expenses pe_chart_template False
+2 -2
View File
@@ -1994,8 +1994,8 @@ class MailThread(models.AbstractModel):
MailThread = self.env['mail.thread']
values = {
'parent_id': parent_id,
'model': self._name if self else False,
'res_id': self.id if self else False,
'model': self._name if self else model,
'res_id': self.id if self else res_id,
'message_type': 'user_notification',
'subject': subject,
'body': body,
@@ -320,7 +320,8 @@ const DiscussWidget = AbstractAction.extend({
* @private
*/
_onClickMarkAllAsRead() {
this.env.models['mail.message'].markAllAsRead(this.domain);
const domain = this.discuss.stringifiedDomain ? JSON.parse(this.discuss.stringifiedDomain) : undefined;
this.env.models['mail.message'].markAllAsRead(domain);
},
/**
* @private
+4 -1
View File
@@ -571,8 +571,11 @@ class MassMailing(models.Model):
def action_send_mail(self, res_ids=None):
author_id = self.env.user.partner_id.id
# If no recipient is passed, we don't want to use the recipients of the first
# mailing for all the others
initial_res_ids = res_ids
for mailing in self:
if not res_ids:
if not initial_res_ids:
res_ids = mailing._get_remaining_recipients()
if not res_ids:
raise UserError(_('There are no recipients selected.'))
+4 -1
View File
@@ -808,7 +808,10 @@ class MrpProduction(models.Model):
# covers at least 2 cases: backorders generation (follow default logic for moves copying)
# and copying a done MO via the form (i.e. copy only the non-cancelled moves since no backorder = cancelled finished moves)
if not default or 'move_finished_ids' not in default:
default['move_finished_ids'] = [(0, 0, move.copy_data()[0]) for move in self.move_finished_ids.filtered(lambda m: m.state != 'cancel' and m.product_qty != 0.0)]
move_finished_ids = self.move_finished_ids
if self.state != 'cancel':
move_finished_ids = self.move_finished_ids.filtered(lambda m: m.state != 'cancel' and m.product_qty != 0.0)
default['move_finished_ids'] = [(0, 0, move.copy_data()[0]) for move in move_finished_ids]
if not default or 'move_raw_ids' not in default:
default['move_raw_ids'] = [(0, 0, move.copy_data()[0]) for move in self.move_raw_ids.filtered(lambda m: m.product_qty != 0.0)]
return super(MrpProduction, self).copy_data(default=default)
+4 -2
View File
@@ -127,6 +127,8 @@ class ProductProduct(models.Model):
bom_kits = self.env['mrp.bom']._get_product2bom(self, bom_type='phantom')
kits = self.filtered(lambda p: bom_kits.get(p))
res = super(ProductProduct, self - kits)._compute_quantities_dict(lot_id, owner_id, package_id, from_date=from_date, to_date=to_date)
qties = self.env.context.get("mrp_compute_quantities", {})
qties.update(res)
for product in bom_kits:
boms, bom_sub_lines = bom_kits[product].explode(product, 1)
ratios_virtual_available = []
@@ -135,7 +137,7 @@ class ProductProduct(models.Model):
ratios_outgoing_qty = []
ratios_free_qty = []
for bom_line, bom_line_data in bom_sub_lines:
component = bom_line.product_id
component = bom_line.product_id.with_context(mrp_compute_quantities=qties)
if component.type != 'product' or float_is_zero(bom_line_data['qty'], precision_rounding=bom_line.product_uom_id.rounding):
# As BoMs allow components with 0 qty, a.k.a. optionnal components, we simply skip those
# to avoid a division by zero. The same logic is applied to non-storable products as those
@@ -145,7 +147,7 @@ class ProductProduct(models.Model):
qty_per_kit = bom_line.product_uom_id._compute_quantity(uom_qty_per_kit, bom_line.product_id.uom_id, raise_if_failure=False)
if not qty_per_kit:
continue
component_res = res.get(component.id, {
component_res = qties.get(component.id, {
"virtual_available": component.virtual_available,
"qty_available": component.qty_available,
"incoming_qty": component.incoming_qty,
+9
View File
@@ -114,6 +114,15 @@ class TestMrpOrder(TestMrpCommon):
self.assertEqual(len(mo_copy.move_finished_ids), 1, "Incorrect number of moves for products to produce [i.e. cancelled moves should not be copied")
self.assertEqual(mo_copy.move_finished_ids.product_uom_qty, 2, "Incorrect qty of products to produce")
# check that a cancelled MO is copied correctly
mo_copy.action_cancel()
self.assertEqual(mo_copy.state, 'cancel')
mo_copy_2 = mo_copy.copy()
self.assertEqual(mo_copy_2.state, 'draft', "Copied production order should be draft.")
self.assertEqual(len(mo_copy_2.move_raw_ids), 2, "Incorrect number of component moves.")
self.assertEqual(len(mo_copy_2.move_finished_ids), 1, "Incorrect number of moves for products to produce [i.e. copying a cancelled MO should copy its cancelled moves]")
self.assertEqual(mo_copy_2.move_finished_ids.product_uom_qty, 2, "Incorrect qty of products to produce")
def test_production_avialability(self):
""" Checks the availability of a production order through mutliple calls to `action_assign`.
"""
+1 -1
View File
@@ -267,7 +267,7 @@
decoration-success="not is_done and (quantity_done - should_consume_qty == 0)"
decoration-warning="not is_done and (quantity_done - should_consume_qty &gt; 0.0001)"
attrs="{'column_invisible': [('parent.state', '=', 'draft')], 'readonly': [('show_details_visible', '=', True)]}"/>
<field name="product_uom" attrs="{'readonly': [('state', '!=', 'draft')]}" options="{'no_open': True, 'no_create': True}" groups="uom.group_uom"/>
<field name="product_uom" attrs="{'readonly': [('state', '!=', 'draft'), ('id', '!=', False)]}" options="{'no_open': True, 'no_create': True}" groups="uom.group_uom"/>
<field name="show_details_visible" invisible="1"/>
<field name="lot_ids" widget="many2many_tags"
groups="stock.group_production_lot" optional="hide"
@@ -112,15 +112,15 @@ var PaymentAdyen = PaymentInterface.extend({
_adyen_pay: function () {
var self = this;
var order = this.pos.get_order();
if (this.pos.get_order().selected_paymentline.amount < 0) {
if (order.selected_paymentline.amount < 0) {
this._show_error(_t('Cannot process transactions with negative amount.'));
return Promise.resolve();
}
if (this.poll_response_error) {
this.poll_response_error = false;
if (order === this.poll_error_order) {
delete this.poll_error_order;
return self._adyen_handle_response({});
}
@@ -191,7 +191,7 @@ var PaymentAdyen = PaymentInterface.extend({
shadow: true,
}).catch(function (data) {
reject();
self.poll_response_error = true;
self.poll_error_order = self.pos.get_order();
return self._handle_flectra_connection_failure(data);
}).then(function (status) {
var notification = status.latest_response;
+1 -1
View File
@@ -444,7 +444,7 @@ class PurchaseOrderLine(models.Model):
def _check_orderpoint_picking_type(self):
warehouse_loc = self.order_id.picking_type_id.warehouse_id.view_location_id
if self.orderpoint_id and not warehouse_loc.parent_path in self.orderpoint_id.location_id.parent_path:
if warehouse_loc and self.orderpoint_id and not warehouse_loc.parent_path in self.orderpoint_id.location_id.parent_path:
raise UserError(_('For the product %s, the warehouse of the operation type (%s) is inconsistent with the location (%s) of the reordering rule (%s). Change the operation type or cancel the request for quotation.',
self.product_id.display_name, self.order_id.picking_type_id.display_name, self.orderpoint_id.location_id.display_name, self.orderpoint_id.display_name))
@@ -13,7 +13,8 @@
</group>
<group attrs="{'invisible': [('action','!=','exist')], 'required':[('action', '=','exist')]}">
<group>
<field name="partner_id" attrs="{'invisible': [('action','!=','exist')], 'required':[('action', '=','exist')]}"/>
<field name="partner_id" attrs="{'invisible': [('action','!=','exist')], 'required':[('action', '=','exist')]}"
context="{'res_partner_search_mode': 'customer'}" />
</group>
</group>
<footer>
+1
View File
@@ -4,3 +4,4 @@
from . import sale
from . import account_move
from . import mrp_production
from . import stock_move
+17
View File
@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import api, fields, models
class StockMoveLine(models.Model):
_inherit = 'stock.move.line'
def _compute_sale_price(self):
kit_lines = self.filtered(lambda move_line: move_line.move_id.bom_line_id.bom_id.type == 'phantom')
for move_line in kit_lines:
unit_price = move_line.product_id.list_price
qty = move_line.product_uom_id._compute_quantity(move_line.qty_done, move_line.product_id.uom_id)
move_line.sale_price = unit_price * qty
super(StockMoveLine, self - kit_lines)._compute_sale_price()
+1 -1
View File
@@ -76,7 +76,7 @@ class SaleOrder(models.Model):
for order in self:
pickings = order.picking_ids.filtered(lambda x: x.state == 'done' and x.location_dest_id.usage == 'customer')
dates_list = [date for date in pickings.mapped('date_done') if date]
order.effective_date = min(dates_list).date() if dates_list else False
order.effective_date = fields.Date.context_today(order, min(dates_list)) if dates_list else False
@api.depends('picking_policy')
def _compute_expected_date(self):
@@ -86,7 +86,7 @@ class TestSaleExpectedDate(ValuationReconciliationTestCommon):
ml.qty_done = ml.product_uom_qty
picking._action_done()
self.assertEqual(picking.state, 'done', "Picking not processed correctly!")
self.assertEqual(fields.Date.today(), sale_order.effective_date, "Wrong effective date on sale order!")
self.assertEqual(fields.Date.context_today(sale_order), sale_order.effective_date, "Wrong effective date on sale order!")
def test_sale_order_commitment_date(self):
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from . import ir_module
from . import sale_order_line
@@ -0,0 +1,25 @@
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import api, models
class IrModule(models.Model):
_inherit = 'ir.module.module'
@api.returns('self')
def downstream_dependencies(
self,
known_deps=None,
exclude_states=('uninstalled', 'uninstallable', 'to remove'),
):
# sale_stock_margin implicitly depends on sale_stock, but sale_stock is not marked as one of
# its dependencies, thus uninstalling sale_stock without uninstalling sale_stock_margin
# will make the registry crash, the install works purely because sale_stock is auto-installed
# when the dependencies of sale_stock_margin are installed
if 'sale_stock' in self.mapped('name'):
# we force sale_stock_margin as a dependant of sale_stock
known_deps = (known_deps or self.browse()) | self.search([
('name', '=', 'sale_stock_margin'),
('state', '=', 'installed'),
], limit=1)
return super().downstream_dependencies(known_deps, exclude_states)
+7 -2
View File
@@ -199,10 +199,15 @@ class SaleOrderLine(models.Model):
"""
lines_by_timesheet = self.filtered(lambda sol: sol.product_id and sol.product_id._is_delivered_timesheet())
domain = lines_by_timesheet._timesheet_compute_delivered_quantity_domain()
domain = expression.AND([domain, [
refund_account_moves = self.order_id.invoice_ids.filtered(lambda am: am.state == 'posted' and am.move_type == 'out_refund').reversed_entry_id
timesheet_domain = [
'|',
('timesheet_invoice_id', '=', False),
('timesheet_invoice_id.state', '=', 'cancel')]])
('timesheet_invoice_id.state', '=', 'cancel')]
if refund_account_moves:
credited_timesheet_domain = [('timesheet_invoice_id.state', '=', 'posted'), ('timesheet_invoice_id', 'in', refund_account_moves.ids)]
timesheet_domain = expression.OR([timesheet_domain, credited_timesheet_domain])
domain = expression.AND([domain, timesheet_domain])
if start_date:
domain = expression.AND([domain, [('date', '>=', start_date)]])
if end_date:
@@ -1,7 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from datetime import timedelta
from flectra.addons.sale_timesheet.tests.common import TestCommonSaleTimesheet
from flectra.exceptions import UserError
from flectra.fields import Date
from flectra.tests import Form, tagged
@@ -283,3 +287,90 @@ class TestReInvoice(TestCommonSaleTimesheet):
self.assertEqual(len(self.sale_order.order_line), 1, "No SO line should have been created (or removed) when validating vendor bill")
self.assertEqual(sale_order_line.qty_delivered, 1, "The delivered quantity of SO line should not have been incremented")
self.assertTrue(invoice_a.mapped('line_ids.analytic_line_ids'), "Analytic lines should be generated")
def test_reversed_invoice_reinvoice_with_period(self):
"""
Tests that when reversing an invoice of timesheet and selecting a time
period, the qty to invoice is correctly found
Business flow:
Create a sale order and deliver some hours (invoiced = 0)
Create an invoice
Confirm (invoiced = 1)
Add Credit Note
Confirm (invoiced = 0)
Go back to the SO
Create an invoice
Select a time period [1 week ago, 1 week in the future]
Confirm
-> Fails if there is nothing to invoice
"""
product = self.env['product.product'].create({
'name': "Service delivered, create task in global project",
'standard_price': 30,
'list_price': 90,
'type': 'service',
'service_policy': 'delivered_timesheet',
'invoice_policy': 'delivery',
'default_code': 'SERV-DELI2',
'service_type': 'timesheet',
'service_tracking': 'task_global_project',
'project_id': self.project_global.id,
'taxes_id': False,
'property_account_income_id': self.account_sale.id,
})
today = Date.context_today(self.env.user)
# Creates a sales order for quantity 3
so_form = Form(self.env['sale.order'])
so_form.partner_id = self.env['res.partner'].create({'name': 'Toto'})
with so_form.order_line.new() as line:
line.product_id = product
line.product_uom_qty = 3.0
sale_order = so_form.save()
sale_order.action_confirm()
# "Deliver" 1 of 3
task = sale_order.tasks_ids
self.env['account.analytic.line'].create({
'name': 'Test Line',
'project_id': task.project_id.id,
'task_id': task.id,
'unit_amount': 1,
'employee_id': self.employee_user.id,
'company_id': self.company_data['company'].id,
})
context = {
"active_model": 'sale.order',
"active_ids": [sale_order.id],
"active_id": sale_order.id,
'open_invoices': True,
}
# Invoice the 1
wizard = self.env['sale.advance.payment.inv'].with_context(context).create({
'advance_payment_method': 'delivered'
})
invoice_dict = wizard.create_invoices()
# Confirm the invoice
invoice = self.env['account.move'].browse(invoice_dict['res_id'])
invoice.action_post()
# Refund the invoice
refund_invoice_wiz = self.env['account.move.reversal'].with_context(active_model="account.move", active_ids=[invoice.id]).create({
'reason': 'please reverse :c',
'refund_method': 'refund',
'date': today,
})
refund_invoice = self.env['account.move'].browse(refund_invoice_wiz.reverse_moves()['res_id'])
refund_invoice.action_post()
# reversing with action_reverse and then action_post does not reset the invoice_status to 'to invoice' in tests
# Recreate wizard to get the new invoices created
wizard = self.env['sale.advance.payment.inv'].with_context(context).create({
'advance_payment_method': 'delivered',
'date_start_invoice_timesheet': today - timedelta(days=7),
'date_end_invoice_timesheet': today + timedelta(days=7)
})
# The actual test :
wizard.create_invoices() # No exception should be raised, there is indeed something to be invoiced since it was reversed
+3 -1
View File
@@ -867,8 +867,10 @@ class StockMove(models.Model):
'confirmed': 1,
}
moves_todo = self\
.filtered(lambda move: move.state not in ['cancel', 'done'])\
.filtered(lambda move: move.state not in ['cancel', 'done'] and not (move.state == 'assigned' and not move.product_uom_qty))\
.sorted(key=lambda move: (sort_map.get(move.state, 0), move.product_uom_qty))
if not moves_todo:
return 'assigned'
# The picking should be the same for all moves.
if moves_todo[:1].picking_id and moves_todo[:1].picking_id.move_type == 'one':
most_important_move = moves_todo[0]
+4
View File
@@ -674,3 +674,7 @@ class StockMoveLine(models.Model):
else:
aggregated_move_lines[line_key]['qty_done'] += move_line.qty_done
return aggregated_move_lines
def _compute_sale_price(self):
# To Override
pass
+31
View File
@@ -799,3 +799,34 @@ class TestPacking(TestPackingCommon):
with Form(picking) as picking_form:
picking_form.package_level_ids.remove(0)
self.assertEqual(len(picking.move_lines), 1, 'Should have only 1 stock move')
def test_picking_state_with_null_qty(self):
receipt_form = Form(self.env['stock.picking'].with_context(default_immediate_transfer=False))
picking_type_id = self.warehouse.out_type_id
receipt_form.picking_type_id = picking_type_id
with receipt_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.productA
move_line.product_uom_qty = 10
with receipt_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.productB
move_line.product_uom_qty = 10
receipt = receipt_form.save()
receipt.action_confirm()
self.assertEqual(receipt.state, 'confirmed')
receipt.move_ids_without_package[1].product_uom_qty = 0
self.assertEqual(receipt.state, 'confirmed')
receipt_form = Form(self.env['stock.picking'].with_context(default_immediate_transfer=True))
picking_type_id = self.warehouse.out_type_id
receipt_form.picking_type_id = picking_type_id
with receipt_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.productA
move_line.quantity_done = 10
with receipt_form.move_ids_without_package.new() as move_line:
move_line.product_id = self.productB
move_line.quantity_done = 10
receipt = receipt_form.save()
receipt.action_confirm()
self.assertEqual(receipt.state, 'assigned')
receipt.move_ids_without_package[1].product_uom_qty = 0
self.assertEqual(receipt.state, 'assigned')
@@ -51,6 +51,7 @@
<field name="json_lead_days_popover" optional="show" nolabel="1" string="Forecast Description" width="10px" widget="popover_widget" attrs="{'invisible': [('id', '=', False)]}"/>
<field name="route_id" options="{'no_create': True, 'no_open': True}"/>
<field name="trigger" optional="hide"/>
<field name="group_id" optional="hide" groups="stock.group_adv_location"/>
<field name="product_min_qty" optional="show"/>
<field name="product_max_qty" optional="show"/>
<field name="qty_multiple" optional="hide"/>
@@ -80,6 +81,7 @@
<field name="location_id" options="{'no_create': True}" groups="stock.group_stock_multi_locations"/>
<field name="route_id" optional="hide"/>
<field name="trigger" optional="hide"/>
<field name="group_id" optional="hide" groups="stock.group_adv_location"/>
<field name="product_min_qty" optional="show"/>
<field name="product_max_qty" optional="show"/>
<field name="qty_multiple" optional="show"/>
@@ -99,6 +101,7 @@
<field name="product_id"/>
<field name="trigger"/>
<field name="product_category_id"/>
<field name="group_id" groups="stock.group_adv_location"/>
<field name="warehouse_id" groups="stock.group_stock_multi_warehouses"/>
<field name="location_id" groups="stock.group_stock_multi_locations"/>
<filter string="To Reorder" name="filter_to_reorder" domain="[('qty_to_order', '&gt;', 0.0)]"/>
@@ -123,6 +126,7 @@
<field name="name" string="Reordering Rule"/>
<field name="product_id"/>
<field name="trigger"/>
<field name="group_id" groups="stock.group_adv_location"/>
<field name="warehouse_id" groups="stock.group_stock_multi_warehouses"/>
<field name="location_id" groups="stock.group_stock_multi_locations"/>
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
@@ -220,6 +220,7 @@ class AccountMoveLine(models.Model):
# OVERRIDE to use the stock input account by default on vendor bills when dealing
# with anglo-saxon accounting.
self.ensure_one()
self = self.with_company(self.move_id.journal_id.company_id)
if self.product_id.type == 'product' \
and self.move_id.company_id.anglo_saxon_accounting \
and self.move_id.is_purchase_document():
@@ -199,7 +199,7 @@ class StockLandedCost(models.Model):
for move in self._get_targeted_move_ids():
# it doesn't make sense to make a landed cost for a product that isn't set as being valuated in real time at real cost
if move.product_id.valuation != 'real_time' or move.product_id.cost_method not in ('fifo', 'average') or move.state == 'cancel':
if move.product_id.valuation != 'real_time' or move.product_id.cost_method not in ('fifo', 'average') or move.state == 'cancel' or not move.product_qty:
continue
vals = {
'product_id': move.product_id.id,
+2 -1
View File
@@ -438,7 +438,8 @@ class SurveyQuestion(models.Model):
count_data = dict.fromkeys(suggested_answers, 0)
for line in user_input_lines:
if line.suggested_answer_id or (line.value_char_box and self.comment_count_as_answer):
if line.suggested_answer_id in count_data\
or (line.value_char_box and self.comment_count_as_answer):
count_data[line.suggested_answer_id] += 1
table_data = [{
@@ -8,7 +8,7 @@
<div t-if="user_input.test_entry" class="test-entry"/>
<div class="certification-top">
<div class="certification-bg-dark">
<div class="certification-company-wrapper" t-field="user_input.survey_id.create_uid.company_id.logo" t-options="{'widget': 'image'}" role="img"/>
<div class="certification-company-wrapper" t-field="user_input.env.company.logo" t-options="{'widget': 'image'}" role="img"/>
</div>
<div class="certification-name" t-field="user_input.survey_id.display_name"/>
</div>
@@ -19,7 +19,7 @@
<div class="user-name" t-esc="user_input.partner_id.name or user_input.email"/>
<div class="certification-description">
<span t-field="user_input.survey_id.create_uid.company_id.display_name"/> is proud to award this certificate to
<span t-field="user_input.env.company.display_name"/> is proud to award this certificate to
<span t-esc="user_input.partner_id.name or user_input.email"/> for graduating <span t-field="user_input.survey_id.display_name"/>.
</div>
</div>
@@ -57,7 +57,7 @@
<hr class="small"/>
<div class="certification-description">
<span t-field="user_input.survey_id.create_uid.company_id.display_name"/> is proud to award this certificate to
<span t-field="user_input.env.company.display_name"/> is proud to award this certificate to
<span t-esc="user_input.partner_id.name or user_input.email"/> for graduating <span t-field="user_input.survey_id.display_name"/>.
</div>
</div>
@@ -69,7 +69,7 @@
<img src="/survey/static/src/img/certification_seal.svg" alt="Certification Seal" role="img"/>
<div class="certificate-signature">
<span t-field="user_input.survey_id.create_uid.company_id.logo" t-options="{'widget': 'image'}" role="img"/>
<span t-field="user_input.env.company.logo" t-options="{'widget': 'image'}" role="img"/>
</div>
</div>
</div>
+20 -7
View File
@@ -14,6 +14,11 @@ const bus = new Bus();
* this file someday.
*/
const maxTouchPoints = navigator.maxTouchPoints || 1;
const isAndroid = /Android/i.test(navigator.userAgent);
const isIOS = /(iPad|iPhone|iPod)/i.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && maxTouchPoints > 1);
const isOtherMobileDevice = /(webOS|BlackBerry|Windows Phone)/i.test(navigator.userAgent);
var config = {
device: {
/**
@@ -37,6 +42,20 @@ var config = {
* @type Number
*/
size_class: null,
/**
* Mobile OS (Android) device detection using userAgent.
* This flag doesn't depend on the size/resolution of the screen.
*
* @return Boolean
*/
isAndroid: isAndroid,
/**
* Mobile OS (iOS) device detection using userAgent.
* This flag doesn't depend on the size/resolution of the screen.
*
* @return Boolean
*/
isIOS: isIOS,
/**
* A frequent use case is to have a different render in 'mobile' mode,
* meaning when the screen is small. This flag (boolean) is true when
@@ -52,13 +71,7 @@ var config = {
*
* @return {boolean}
*/
isMobileDevice: navigator.userAgent.match(/Android/i) ||
navigator.userAgent.match(/webOS/i) ||
navigator.userAgent.match(/iPhone/i) ||
navigator.userAgent.match(/iPad/i) ||
navigator.userAgent.match(/iPod/i) ||
navigator.userAgent.match(/BlackBerry/i) ||
navigator.userAgent.match(/Windows Phone/i),
isMobileDevice: isAndroid || isIOS || isOtherMobileDevice,
/**
* Mapping between the numbers 0,1,2,3,4,5,6 and some descriptions
*/
@@ -118,7 +118,11 @@ var SnippetEditor = Widget.extend({
}
var _animationsCount = 0;
var postAnimationCover = _.throttle(() => this.cover(), 100);
var postAnimationCover = _.throttle(() => {
this.trigger_up('cover_update', {
overlayVisible: true,
});
}, 100);
this.$target.on('transitionstart.snippet_editor, animationstart.snippet_editor', () => {
// We cannot rely on the fact each transition/animation start will
// trigger a transition/animation end as the element may be removed
@@ -2327,9 +2331,13 @@ var SnippetsMenu = Widget.extend({
* Called when the overlay dimensions/positions should be recomputed.
*
* @private
* @param {FlectraEvent} ev
*/
_onOverlaysCoverUpdate: function () {
_onOverlaysCoverUpdate: function (ev) {
this.snippetEditors.forEach(editor => {
if (ev.data.overlayVisible) {
editor.toggleOverlayVisibility(true);
}
editor.cover();
});
},
@@ -4,6 +4,7 @@ flectra.define('web_editor.snippets.options', function (require) {
var core = require('web.core');
const {ColorpickerWidget} = require('web.Colorpicker');
const Dialog = require('web.Dialog');
const {scrollTo} = require('web.dom');
const rpc = require('web.rpc');
const time = require('web.time');
var Widget = require('web.Widget');
@@ -4258,6 +4259,18 @@ registry.BackgroundPosition = SnippetOptionWidget.extend({
left: position[0] / 100 * delta.x || 0,
top: position[1] / 100 * delta.y || 0,
};
// Make sure the element is in a visible area.
const rect = this.$target[0].getBoundingClientRect();
const viewportTop = $(window).scrollTop();
const viewportBottom = viewportTop + $(window).height();
const visibleHeight = rect.top < viewportTop
? Math.max(0, Math.min(viewportBottom, rect.bottom) - viewportTop) // Starts above
: rect.top < viewportBottom
? Math.min(viewportBottom, rect.bottom) - rect.top // Starts inside
: 0; // Starts after
if (visibleHeight < 200) {
await scrollTo(this.$target[0], {extraOffset: 50});
}
this._toggleBgOverlay(true);
},
/**
@@ -4339,7 +4352,7 @@ registry.BackgroundPosition = SnippetOptionWidget.extend({
height: `${this.$target.innerHeight()}px`,
});
const topPos = (parseInt(this.$overlay.css('top')) - parseInt(this.$overlayContent.css('top')));
const topPos = Math.max(0, $(window).scrollTop() - this.$target.offset().top);
this.$overlayContent.find('.o_we_overlay_buttons').css('top', `${topPos}px`);
},
/**
@@ -173,8 +173,18 @@ var LinkDialog = Dialog.extend({
this.$styleInputs.prop('checked', false).filter('[value=""]').prop('checked', true);
if (this.data.iniClassName) {
_.each(this.$('input[name="link_style_color"], select[name="link_style_size"] > option, select[name="link_style_shape"] > option'), el => {
var $option = $(el);
if ($option.val() && this.data.iniClassName.match(new RegExp('(^|btn-| |btn-outline-)' + $option.val()))) {
const $option = $(el);
const value = $option.val();
if (!value) {
return;
}
const subValues = value.split(',');
let active = true;
for (let i = 0; i < subValues.length; i++) {
const classPrefix = new RegExp('(^|btn-| |btn-outline-|btn-fill-)' + subValues[i]);
active = active && classPrefix.test(this.data.iniClassName);
}
if (active) {
if ($option.is("input")) {
$option.prop("checked", true);
} else {
+81
View File
@@ -283,6 +283,86 @@ function removeLoader() {
}
}
/**
* Converts a base64 SVG into a base64 PNG.
*
* @param {string|HTMLImageElement} src - an URL to a SVG or a *loaded* image
* with such an URL. This allows the call to this method to be potentially
* not return a Promise.
* @param {boolean} [noAsync=false] In case, the given first parameter is a
* loaded image, this parameter allows to ignore problematic images and
* return a (problematic) PNG result synchronously.
* @returns {Promise<string>|string} a base64 PNG (as result of a Promise or not)
*/
function svgToPNG(src, noAsync = false) {
function checkImg(imgEl) {
// Firefox does not support drawing SVG to canvas unless it has width
// and height attributes set on the root <svg>.
return (imgEl.naturalHeight !== 0);
}
function toPNGViaCanvas(imgEl) {
const canvas = document.createElement('canvas');
canvas.width = imgEl.width;
canvas.height = imgEl.height;
canvas.getContext('2d').drawImage(imgEl, 0, 0);
return canvas.toDataURL('image/png');
}
// In case we receive a loaded image with the given src and that this image
// is not problematic, we can convert it to PNG synchronously.
if (src instanceof HTMLImageElement) {
const loadedImgEl = src;
if (noAsync || checkImg(loadedImgEl)) {
return toPNGViaCanvas(loadedImgEl);
}
src = loadedImgEl.src;
}
// At this point, we either did not receive a loaded image or the received
// loaded image is problematic => we have to do some asynchronous code and
// the function will thus return a Promise.
return new Promise(resolve => {
const imgEl = new Image();
imgEl.onload = () => {
if (checkImg(imgEl)) {
resolve(imgEl);
return;
}
// Set arbitrary height on image and attach it to the DOM to force
// width computation.
imgEl.height = 1000;
imgEl.style.opacity = 0;
document.body.appendChild(imgEl);
const request = new XMLHttpRequest();
request.open('GET', imgEl.src, true);
request.onload = () => {
// Convert the data URI to a SVG element
const parser = new DOMParser();
const result = parser.parseFromString(request.responseText, 'text/xml');
const svgEl = result.getElementsByTagName("svg")[0];
// Add the attributes Firefox needs and remove the image from
// the DOM.
svgEl.setAttribute('width', imgEl.width);
svgEl.setAttribute('height', imgEl.height);
imgEl.remove();
// Convert the SVG element to a data URI
const svg64 = btoa(new XMLSerializer().serializeToString(svgEl));
const finalImg = new Image();
finalImg.onload = () => {
resolve(finalImg);
};
finalImg.src = `data:image/svg+xml;base64,${svg64}`;
};
request.send();
};
imgEl.src = src;
}).then(loadedImgEl => toPNGViaCanvas(loadedImgEl));
}
return {
loadAnchors: loadAnchors,
autocompleteWithPages: autocompleteWithPages,
@@ -291,5 +371,6 @@ return {
sendRequest: sendRequest,
websiteDomain: websiteDomain,
removeLoader: removeLoader,
svgToPNG: svgToPNG,
};
});
+17 -3
View File
@@ -187,6 +187,23 @@ $-seen-urls: ();
}
}
body:not(.o_fullscreen) {
#oe_main_menu_navbar:not(.o_hidden) + #wrapwrap {
.o_header_affixed, .modal {
// For these elements, in #wrapwrap, position top should be size of
// the navbar.
top: $o-navbar-height;
}
.modal {
// As the default bootstrap style no longer uses 'bottom: 0' and
// added 'height: 100%' since the V4.2 because of a bug on Android.
// We have to adapt the height of the modal by removing the height
// of the navbar.
height: calc(100vh - #{$o-navbar-height});
}
}
}
.navbar {
.navbar-collapse {
@@ -773,9 +790,6 @@ $-transition-duration: 200ms;
}
}
}
#oe_main_menu_navbar + #wrapwrap .o_header_affixed {
top: $o-navbar-height;
}
// Navbar
.navbar .o_extra_menu_items.show > ul {
@@ -27,6 +27,10 @@ body.o_connected_user {
&.editor_enable.editor_has_snippets {
padding-right: 0 !important;
.modal:not(.o_technical_modal) {
right: 0;
}
}
#oe_main_menu_navbar, #web_editor-top-edit {
transform: translateY(-100%);
+15 -15
View File
@@ -387,7 +387,7 @@
</div>
<!-- Social -->
<div class="col-lg-6 pb16">
<div class="s_share text-left no_icon_color" data-name="Social Media">
<div class="s_share text-left no_icon_color" data-snippet="s_share" data-name="Social Media">
<h5 class="s_share_title d-none">Follow us</h5>
<a href="/website/social/facebook" class="s_share_facebook" target="_blank">
<i class="fa fa-facebook m-1"/>
@@ -490,7 +490,7 @@
<div class="oe_structure oe_structure_solo" id="oe_structure_header_vertical_1">
<section class="s_text_block" data-snippet="s_text_block" data-name="Text">
<div class="container">
<div class="s_share no_icon_color" data-name="Social Media">
<div class="s_share no_icon_color" data-snippet="s_share" data-name="Social Media">
<h6 class="s_share_title d-none">Follow us</h6>
<a href="/website/social/facebook" class="s_share_facebook" target="_blank">
<i class="fa fa-facebook m-1"/>
@@ -592,7 +592,7 @@
<section class="s_text_block" data-snippet="s_text_block" data-name="Text">
<div class="container">
<!-- Social -->
<div class="s_share mt-3" data-name="Social Media">
<div class="s_share mt-3" data-snippet="s_share" data-name="Social Media">
<h5 class="s_share_title d-none">Follow us</h5>
<a href="/website/social/facebook" class="s_share_facebook" target="_blank">
<i class="fa fa-facebook rounded-circle shadow-sm"/>
@@ -778,7 +778,7 @@
</small>
</div>
<div class="col-lg-4 text-lg-right">
<div class="s_share no_icon_color" data-name="Social Media">
<div class="s_share no_icon_color" data-snippet="s_share" data-name="Social Media">
<small class="s_share_title text-muted"><b>Follow us</b></small>
<a href="/website/social/facebook" class="s_share_facebook" target="_blank">
<i class="fa fa-facebook m-1"/>
@@ -906,7 +906,7 @@
<div class="oe_structure oe_structure_solo" id="oe_structure_header_boxed_1">
<section class="s_text_block" data-snippet="s_text_block" name="Text">
<div class="container">
<div class="s_share text-left" data-name="Social Media">
<div class="s_share text-left" data-snippet="s_share" data-name="Social Media">
<h6 class="s_share_title d-none">Follow us</h6>
<a href="/website/social/facebook" class="s_share_facebook" target="_blank">
<i class="fa fa-facebook m-1"/>
@@ -1103,10 +1103,10 @@
<template id="template_header_hamburger_full_oe_structure_header_hamburger_full_1" inherit_id="website.template_header_hamburger_full" name="Template Header Hamburger Full (oe_structure_header_hamburger_full_1)">
<xpath expr="//*[hasclass('oe_structure')][@id='oe_structure_header_hamburger_full_1']" position="replace">
<div class="oe_structure oe_structure_solo w-100" id="oe_structure_header_hamburger_full_1">
<div class="s_hr pt32 pb32" data-name="Separator">
<div class="s_hr pt32 pb32" data-snippet="s_hr" data-name="Separator">
<hr class="w-100 mx-auto" style="border-top-width: 1px; border-top-style: solid; border-color: var(--400);"/>
</div>
<div class="s_share text-center" data-name="Social Media">
<div class="s_share text-center" data-snippet="s_share" data-name="Social Media">
<h5 class="s_share_title d-none">Follow us</h5>
<a href="/website/social/facebook" class="s_share_facebook" target="_blank">
<i class="fa fa-facebook rounded-circle shadow-sm"/>
@@ -1186,7 +1186,7 @@
<p class="mb-0"><small>We help you grow your business</small></p>
</div>
<div class="col-lg-4 text-lg-right pt16">
<div class="s_share no_icon_color" data-name="Social Media">
<div class="s_share no_icon_color" data-snippet="s_share" data-name="Social Media">
<small class="s_share_title d-none"><b>Follow us</b></small>
<a href="/website/social/facebook" class="s_share_facebook" target="_blank">
<i class="fa fa-facebook m-1"/>
@@ -1374,7 +1374,7 @@
<li><i class="fa fa-envelope fa-fw mr-2"/><span><a href="mailto:info@yourcompany.example.com">info@yourcompany.example.com</a></span></li>
<li><i class="fa fa-phone fa-fw mr-2"/><span class="o_force_ltr"><a href="tel:+1 (650) 555-0111">+1 (650) 555-0111</a></span></li>
</ul>
<div class="s_share text-left" data-name="Social Media">
<div class="s_share text-left" data-snippet="s_share" data-name="Social Media">
<h5 class="s_share_title d-none">Follow us</h5>
<a href="/website/social/facebook" class="s_share_facebook" target="_blank">
<i class="fa fa-facebook rounded-circle shadow-sm"/>
@@ -1400,7 +1400,7 @@
<template id="template_footer_descriptive" inherit_id="website.layout" name="Descriptive" active="False">
<xpath expr="//div[@id='footer']" position="replace">
<div id="footer" class="oe_structure oe_structure_solo" t-ignore="true" t-if="not no_footer">
<section class="s_title pt48 pb24">
<section class="s_title pt48 pb24" data-vcss="001" data-snippet="s_title">
<div class="container s_allow_columns">
<h4><b>Designed</b> for companies</h4>
</div>
@@ -1419,7 +1419,7 @@
<li><i class="fa fa-phone fa-fw mr-2"/><span class="o_force_ltr"><a href="tel:+1 (650) 555-0111">+1 (650) 555-0111</a></span></li>
<li><i class="fa fa-envelope fa-fw mr-2"/><span><a href="mailto:hello@mycompany.com">hello@mycompany.com</a></span></li>
</ul>
<div class="s_share text-left no_icon_color" data-name="Social Media">
<div class="s_share text-left no_icon_color" data-snippet="s_share" data-name="Social Media">
<h5 class="s_share_title d-none">Follow us</h5>
<a href="/website/social/github" class="s_share_github" target="_blank">
<i class="fa fa-2x fa-github m-1"/>
@@ -1447,7 +1447,7 @@
<div id="footer" class="oe_structure oe_structure_solo" t-ignore="true" t-if="not no_footer">
<section class="s_text_block pt32" data-snippet="s_text_block" data-name="Text">
<div class="container s_allow_columns">
<div class="s_share text-center mb-4" data-name="Social Media">
<div class="s_share text-center mb-4" data-snippet="s_share" data-name="Social Media">
<h5 class="s_share_title d-none">Follow us</h5>
<a href="/website/social/facebook" class="s_share_facebook" target="_blank">
<i class="fa fa-facebook rounded-circle rounded shadow-sm"/>
@@ -1552,7 +1552,7 @@
</ul>
</div>
<div class="col-lg-3 pt16 pb16">
<div class="s_share text-right no_icon_color" data-name="Social Media">
<div class="s_share text-right no_icon_color" data-snippet="s_share" data-name="Social Media">
<h5 class="s_share_title d-none">Follow us</h5>
<a href="/website/social/github" class="s_share_github" target="_blank">
<i class="fa fa-2x fa-github m-1"/>
@@ -1594,7 +1594,7 @@
<h5><a href="mailto:hello@mycompany.com">hello@mycompany.com</a></h5>
</div>
<div class="col-lg-3 pt16 pb16">
<div class="s_share text-right no_icon_color" data-name="Social Media">
<div class="s_share text-right no_icon_color" data-snippet="s_share" data-name="Social Media">
<p class="s_share_title d-block mb-2">Follow us</p>
<a href="/website/social/twitter" class="s_share_twitter" target="_blank">
<i class="fa fa-twitter m-1"/>
@@ -1715,7 +1715,7 @@
</ul>
</div>
<div class="col-lg-3 pb24">
<div class="s_share text-right" data-name="Social Media">
<div class="s_share text-right" data-snippet="s_share" data-name="Social Media">
<h5 class="s_share_title d-none">Share</h5>
<a href="/website/social/facebook" class="s_share_facebook" target="_blank">
<i class="fa fa-1x fa-facebook rounded-circle shadow-sm"/>
+1 -1
View File
@@ -33,7 +33,7 @@ class Lead(models.Model):
action = self.env["ir.actions.actions"]._for_xml_id("website.website_visitor_page_action")
action['domain'] = [('visitor_id', 'in', visitors.ids)]
# avoid grouping if only few records
if len(visitors.website_track_ids.ids) > 15 and len(visitors.page_ids.ids) > 1:
if len(visitors.website_track_ids) > 15 and len(visitors.website_track_ids.page_id) > 1:
action['context'] = {'search_default_group_by_page': '1'}
return action
@@ -52,6 +52,15 @@ const FormEditor = options.Class.extend({
}
return field.records;
},
/**
* Generates a new ID.
*
* @private
* @returns {string} The new ID
*/
_generateUniqueID() {
return Math.random().toString(36).substring(2, 15);
},
/**
* Returns a field object
*
@@ -119,10 +128,10 @@ const FormEditor = options.Class.extend({
/**
* @private
* @param {Object} field
* @returns {Promise<HTMLElement>}
* @returns {HTMLElement}
*/
_renderField: function (field) {
field.id = Math.random().toString(36).substring(2, 15); // Big unique ID
field.id = this._generateUniqueID();
const template = document.createElement('template');
template.innerHTML = qweb.render("website_form.field_" + field.type, {field: field}).trim();
return template.content.firstElementChild;
@@ -426,10 +435,10 @@ options.registry.WebsiteFormEditor = FormEditor.extend({
field.formatInfo.requiredMark = this._isRequiredMark();
field.formatInfo.optionalMark = this._isOptionalMark();
field.formatInfo.mark = this._getMark();
const htmlField = this._renderField(field);
data.$target.after(htmlField);
const fieldEl = this._renderField(field);
data.$target.after(fieldEl);
this.trigger_up('activate_snippet', {
$snippet: $(htmlField),
$snippet: $(fieldEl),
});
}
},
@@ -821,6 +830,19 @@ options.registry.WebsiteFieldEditor = FieldEditor.extend({
// We need to reload the existing type list.
this.rerender = true;
},
/**
* Rerenders the clone to avoid id duplicates.
* Rendering the list before replacing the field is needed
* for selects, in order to build this.listTable.
*
* @override
*/
onClone() {
this._renderList();
const field = this._getActiveField();
const fieldEl = this._renderField(field);
this._replaceFieldElement(fieldEl);
},
//----------------------------------------------------------------------
// Options
@@ -978,7 +1000,7 @@ options.registry.WebsiteFieldEditor = FieldEditor.extend({
}
},
/**
* Replace the target content with the field provided
* Replaces the target content with the field provided.
*
* @private
* @param {Object} field
@@ -986,11 +1008,20 @@ options.registry.WebsiteFieldEditor = FieldEditor.extend({
*/
_replaceField: async function (field) {
await this._fetchFieldRecords(field);
const htmlField = this._renderField(field);
const fieldEl = this._renderField(field);
this._replaceFieldElement(fieldEl);
},
/**
* Replaces the target with provided field.
*
* @private
* @param {HTMLElement} fieldEl
*/
_replaceFieldElement(fieldEl) {
[...this.$target[0].childNodes].forEach(node => node.remove());
[...htmlField.childNodes].forEach(node => this.$target[0].appendChild(node));
[...htmlField.attributes].forEach(el => this.$target[0].removeAttribute(el.nodeName));
[...htmlField.attributes].forEach(el => this.$target[0].setAttribute(el.nodeName, el.nodeValue));
[...fieldEl.childNodes].forEach(node => this.$target[0].appendChild(node));
[...fieldEl.attributes].forEach(el => this.$target[0].removeAttribute(el.nodeName));
[...fieldEl.attributes].forEach(el => this.$target[0].setAttribute(el.nodeName, el.nodeValue));
},
/**
@@ -1153,7 +1184,7 @@ options.registry.WebsiteFieldEditor = FieldEditor.extend({
const params = {
field: {
name: name,
id: Math.random().toString(36).substring(2, 15), // Big unique ID
id: this._generateUniqueID(),
required: isRequiredField,
formatInfo: {
multiPosition: multiInputsWrap.dataset.display,
@@ -1270,10 +1301,10 @@ options.registry.AddFieldForm = FormEditor.extend({
addField: async function (previewMode, value, params) {
const field = this._getCustomField('char', 'Custom Text');
field.formatInfo = this._getDefaultFormat();
const htmlField = this._renderField(field);
this.$target.find('.s_website_form_submit, .s_website_form_recaptcha').first().before(htmlField);
const fieldEl = this._renderField(field);
this.$target.find('.s_website_form_submit, .s_website_form_recaptcha').first().before(fieldEl);
this.trigger_up('activate_snippet', {
$snippet: $(htmlField),
$snippet: $(fieldEl),
});
},
});
@@ -5,6 +5,7 @@ var core = require('web.core');
var Dialog = require('web.Dialog');
var publicWidget = require('web.public.widget');
var utils = require('web.utils');
var wUtils = require('website.utils');
var QWeb = core.qweb;
var _t = core._t;
@@ -152,20 +153,38 @@ var SlideUploadDialog = Dialog.extend({
'datas': this.file.data
});
} else if (values['slide_type'] === 'webpage') {
let fileData = this.file.type === 'image/svg+xml' ? this.__svgToPNG() : this.file.data;
if (fileData instanceof Promise) {
const fileDataProm = fileData;
this.__svgLoadingExec = () => fileDataProm.then(result => values['image_1920'] = result);
fileData = this._svgToPng();
}
_.extend(values, {
'mime_type': 'text/html',
'image_1920': this.file.type === 'image/svg+xml' ? this._svgToPng() : this.file.data,
'image_1920': fileData,
});
} else if (/^image\/.*/.test(this.file.type)) {
let fileData = this.file.type === 'image/svg+xml' ? this.__svgToPNG() : this.file.data;
let fileDataProm;
if (fileData instanceof Promise) {
fileDataProm = fileData;
fileData = this._svgToPng();
}
if (values['slide_type'] === 'presentation') {
if (fileDataProm) {
this.__svgLoadingExec = () => fileDataProm.then(result => values['datas'] = result);
}
_.extend(values, {
'slide_type': 'infographic',
'mime_type': this.file.type === 'image/svg+xml' ? 'image/png' : this.file.type,
'datas': this.file.type === 'image/svg+xml' ? this._svgToPng() : this.file.data
'datas': fileData,
});
} else {
if (fileDataProm) {
this.__svgLoadingExec = () => fileDataProm.then(result => values['image_1920'] = result);
}
_.extend(values, {
'image_1920': this.file.type === 'image/svg+xml' ? this._svgToPng() : this.file.data,
'image_1920': fileData,
});
}
}
@@ -373,13 +392,22 @@ var SlideUploadDialog = Dialog.extend({
// TODO: Remove this part, as now SVG support in image resize tools is included
//Python PIL does not support SVG, so converting SVG to PNG
_svgToPng: function () {
var img = this.$el.find('img#slide-image')[0];
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext('2d').drawImage(img, 0, 0);
return canvas.toDataURL('image/png').split(',')[1];
return this.__svgToPNG(true);
},
/**
* Bug fixed version of the original _svgToPng, it can return a Promise
*
* @returns {Promise<string>|string}
*/
__svgToPNG: function (noAsync = false) {
const imgEl = this.$el.find('img#slide-image')[0];
const result = wUtils.svgToPNG(imgEl, noAsync);
if (typeof(result) === 'string') {
return result.split(',')[1];
}
return result.then(png => png.split(',')[1]);
},
//--------------------------------------------------------------------------
// Handler
//--------------------------------------------------------------------------
@@ -601,9 +629,18 @@ var SlideUploadDialog = Dialog.extend({
var values = this._formValidateGetValues($btn.hasClass('o_w_slide_upload_published')); // get info before changing state
var oldType = this.get('state');
this.set('state', '_upload');
return this._rpc({
route: '/slides/add_slide',
params: values,
return new Promise(async resolve => {
if (this.__svgLoadingExec) {
await this.__svgLoadingExec();
}
delete this.__svgLoadingExec;
resolve();
}).then(() => {
return this._rpc({
route: '/slides/add_slide',
params: values,
});
}).then(function (data) {
self._onFormSubmitDone(data, oldType);
});