[MERGE] forward port branch 11.0 up to 20e4cd5bcd
This commit is contained in:
@@ -770,7 +770,7 @@ class WizardMultiChartsAccounts(models.TransientModel):
|
||||
if company_id:
|
||||
company = self.env['res.company'].browse(company_id)
|
||||
currency_id = company.on_change_country(company.country_id.id)['value']['currency_id']
|
||||
res.update({'currency_id': currency_id.id})
|
||||
res.update({'currency_id': currency_id})
|
||||
|
||||
chart_templates = account_chart_template.search([('visible', '=', True)])
|
||||
if chart_templates:
|
||||
|
||||
@@ -613,7 +613,7 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onFilterChange: function () {
|
||||
_onFilterChange: function (event) {
|
||||
this.trigger_up('change_filter', {'data': _.str.strip($(event.target).val())});
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -30,16 +30,23 @@ class ResPartner(models.Model):
|
||||
city = lines.pop()
|
||||
return (cp, city)
|
||||
else:
|
||||
result = re.match('((?:L-|AT-)?[0-9\-]+) (.+)', lines[-1])
|
||||
result = re.match('((?:L-|AT-)?[0-9\-]+[A-Z]{,2}) (.+)', lines[-1])
|
||||
if result:
|
||||
lines.pop()
|
||||
return (result.group(1), result.group(2))
|
||||
return False
|
||||
|
||||
def _set_address_field(partner, field, value):
|
||||
partner[field] = value
|
||||
non_set_address_fields.remove(field)
|
||||
|
||||
if stdnum_vat is None:
|
||||
return {}
|
||||
|
||||
for partner in self:
|
||||
# If a field is non set in this algorithm
|
||||
# wipe it anyway
|
||||
non_set_address_fields = set(['street', 'street2', 'city', 'zip', 'state_id', 'country_id'])
|
||||
if not partner.vat:
|
||||
return {}
|
||||
if len(partner.vat) > 5 and partner.vat[:2].lower() in stdnum_vat.country_codes:
|
||||
@@ -68,14 +75,20 @@ class ResPartner(models.Model):
|
||||
lines = [x.strip() for x in lines[0].split(',') if x]
|
||||
if len(lines) == 1:
|
||||
lines = [x.strip() for x in lines[0].split(' ') if x]
|
||||
partner.street = lines.pop(0)
|
||||
|
||||
_set_address_field(partner, 'street', lines.pop(0))
|
||||
|
||||
if len(lines) > 0:
|
||||
res = _check_city(lines, result['countryCode'])
|
||||
if res:
|
||||
partner.zip = res[0]
|
||||
partner.city = res[1]
|
||||
_set_address_field(partner, 'zip', res[0])
|
||||
_set_address_field(partner, 'city', res[1])
|
||||
if len(lines) > 0:
|
||||
partner.street2 = lines.pop(0)
|
||||
_set_address_field(partner, 'street2', lines.pop(0))
|
||||
|
||||
country = self.env['res.country'].search([('code', '=', result['countryCode'])], limit=1)
|
||||
partner.country_id = country and country.id or False
|
||||
_set_address_field(partner, 'country_id', country and country.id or False)
|
||||
|
||||
for field in non_set_address_fields:
|
||||
if partner[field]:
|
||||
partner[field] = False
|
||||
|
||||
@@ -6,8 +6,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 11.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-11-30 13:12+0000\n"
|
||||
"PO-Revision-Date: 2017-11-30 13:12+0000\n"
|
||||
"POT-Creation-Date: 2018-02-07 10:30+0000\n"
|
||||
"PO-Revision-Date: 2018-02-07 10:30+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -272,11 +272,6 @@ msgid "\n"
|
||||
"</div>"
|
||||
msgstr ""
|
||||
|
||||
#. module: calendar
|
||||
#: model:ir.model.fields,field_description:calendar.field_calendar_event_is_highlighted
|
||||
msgid "# Meetings Highlight"
|
||||
msgstr ""
|
||||
|
||||
#. module: calendar
|
||||
#: model:mail.template,subject:calendar.calendar_template_meeting_reminder
|
||||
msgid "${object.event_id.name} - Reminder"
|
||||
@@ -805,6 +800,11 @@ msgstr ""
|
||||
msgid "Invite wizard"
|
||||
msgstr ""
|
||||
|
||||
#. module: calendar
|
||||
#: model:ir.model.fields,field_description:calendar.field_calendar_event_is_highlighted
|
||||
msgid "Is the Event Highlighted"
|
||||
msgstr ""
|
||||
|
||||
#. module: calendar
|
||||
#: selection:calendar.event,byday:0
|
||||
msgid "Last"
|
||||
|
||||
@@ -823,7 +823,7 @@ class Meeting(models.Model):
|
||||
attendee_ids = fields.One2many('calendar.attendee', 'event_id', 'Participant', ondelete='cascade')
|
||||
partner_ids = fields.Many2many('res.partner', 'calendar_event_res_partner_rel', string='Attendees', states={'done': [('readonly', True)]}, default=_default_partners)
|
||||
alarm_ids = fields.Many2many('calendar.alarm', 'calendar_alarm_calendar_event_rel', string='Reminders', ondelete="restrict", copy=False)
|
||||
is_highlighted = fields.Boolean(compute='_compute_is_highlighted', string='# Meetings Highlight')
|
||||
is_highlighted = fields.Boolean(compute='_compute_is_highlighted', string='Is the Event Highlighted')
|
||||
|
||||
@api.multi
|
||||
def _compute_attendee(self):
|
||||
@@ -1478,7 +1478,7 @@ class Meeting(models.Model):
|
||||
partners_to_notify = meeting.partner_ids.ids
|
||||
event_attendees_changes = attendees_create and real_ids and attendees_create[real_ids[0]]
|
||||
if event_attendees_changes:
|
||||
partners_to_notify.append(event_attendees_changes['removed_partners'].ids)
|
||||
partners_to_notify.extend(event_attendees_changes['removed_partners'].ids)
|
||||
self.env['calendar.alarm_manager'].notify_next_alarm(partners_to_notify)
|
||||
|
||||
if (values.get('start_date') or values.get('start_datetime') or
|
||||
|
||||
@@ -13,6 +13,7 @@ CRM Phone Numbers Validation
|
||||
This module allows for validate and format phone numbers for leads and contacts.""",
|
||||
'data': [
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/crm_lead_views.xml'
|
||||
],
|
||||
'depends': [
|
||||
'phone_validation',
|
||||
|
||||
@@ -8,13 +8,12 @@ class Lead(models.Model):
|
||||
_name = 'crm.lead'
|
||||
_inherit = ['crm.lead', 'phone.validation.mixin']
|
||||
|
||||
@api.onchange('phone', 'country_id')
|
||||
@api.onchange('phone', 'country_id', 'company_id')
|
||||
def _onchange_phone_validation(self):
|
||||
if self.phone:
|
||||
self.phone = self.phone_format(self.phone)
|
||||
|
||||
@api.onchange('mobile', 'country_id')
|
||||
@api.onchange('mobile', 'country_id', 'company_id')
|
||||
def _onchange_mobile_validation(self):
|
||||
if self.mobile:
|
||||
self.mobile = self.phone_format(self.mobile)
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@ class Contact(models.Model):
|
||||
_name = 'res.partner'
|
||||
_inherit = ['res.partner', 'phone.validation.mixin']
|
||||
|
||||
@api.onchange('phone', 'country_id')
|
||||
@api.onchange('phone', 'country_id', 'company_id')
|
||||
def _onchange_phone_validation(self):
|
||||
if self.phone:
|
||||
self.phone = self.phone_format(self.phone)
|
||||
|
||||
@api.onchange('mobile', 'country_id')
|
||||
@api.onchange('mobile', 'country_id', 'company_id')
|
||||
def _onchange_mobile_validation(self):
|
||||
if self.mobile:
|
||||
self.mobile = self.phone_format(self.mobile)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="crm_lead_opportunity_view_form" model="ir.ui.view">
|
||||
<field name="name">crm.lead.form.opportunity.inherit.crm.phone.validation</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="inherit_id" ref="crm.crm_case_form_view_oppor" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group[@name='Misc']" position="inside">
|
||||
<field name="company_id" invisible="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -14,5 +14,5 @@
|
||||
],
|
||||
'demo': [],
|
||||
'installable': True,
|
||||
'auto-install': True,
|
||||
'auto_install': True,
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ msgstr "12% Diverse goederen"
|
||||
#: model:account.tax,name:l10n_be.1_attn_VAT-IN-V83-12-CC
|
||||
#: model:account.tax.template,name:l10n_be.attn_VAT-IN-V83-12-CC
|
||||
msgid "12% Cocont. - Biens d'investissement"
|
||||
msgstr "0% Medecont. - Investeringsgoederen"
|
||||
msgstr "12% Medecont. - Investeringsgoederen"
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.tax,name:l10n_be.1_attn_VAT-IN-V83-12-CC-C1
|
||||
@@ -389,7 +389,7 @@ msgstr "21% Medecont. G."
|
||||
#: model:account.tax,name:l10n_be.1_attn_VAT-IN-V83-21-CC-C2
|
||||
#: model:account.tax.template,name:l10n_be.attn_VAT-IN-V83-21-CC-C2
|
||||
msgid "21% Cocont. Récupérable - Biens d'investissement"
|
||||
msgstr "12% Medecont. Verhaalbaar - Investeringsgoederen"
|
||||
msgstr "21% Medecont. Verhaalbaar - Investeringsgoederen"
|
||||
|
||||
#. module: l10n_be
|
||||
#: model:account.tax,name:l10n_be.1_attn_VAT-IN-V81-21-CC-C2
|
||||
|
||||
@@ -58,6 +58,34 @@ co_puc_235510,235510,Socios,TRUE,account.data_account_type_payable,l10n_co_chart
|
||||
co_puc_236005,236005,Dividendos,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_236505,236505,Salarios y Pagos Laborales,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_236510,236510,Dividendos y/o Participaciones,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23651505,23651505,Retencion en la fuente por honorarios persona natural 10%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23651510,23651510,Retencion en la fuente por honorarios persona Juridica 11%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23651515,23651515,Retencion en la fuente por servicios de licencias software 3.5%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23651520,23651520,Retencion en la fuente por servicios de diseño web y consultorí informática 3.5%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23652005,23652005,Retencion en la fuente por comisiones persona natural 10%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23652010,23652010,Retencion en la fuente por comisiones persona juridica 11%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23652505,23652505,Retencion en la fuente por servicios en general 4%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23652510,23652510,Retencion en la fuente por servicios en general personas naturales no declarantes Renta 6%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23652515,23652515,Retencion en la fuente por servicio transporte nacional carga 1%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23652520,23652520,Retencion en la fuente por servicio transporte terrestre nacional pasajeros 3.5%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23652525,23652525,Retencion en la fuente por servicio transporte aereo y maritimo nacional pasajeros 1%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23652530,23652530,Retencion en la fuente por servicio temporales de empleo (sobre AIU) 1%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23652535,23652535,Retencion en la fuente por servicio de vigilancia y aseo (sobre AIU) 2%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23652540,23652540,Retencion en la fuente por servicio integrales de salud 2%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23652545,23652545,Retencion en la fuente por servicio de hoteles y restaurantes 3.5%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23652550,23652550,Retencion en la fuente por contratos de obra inmuebles 2%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23653005,23653005,Retencion en la fuente por arrendamientos Bienes Muebles 4%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23653010,23653010,Retencion en la fuente por arrendamientos Bienes Inmuebles 3.5%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23653510,23653510,Retencion en la fuente por rendimientos financieros generales 7%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23654005,23654005,Retencion en la fuente por compras declarantes 2.5%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23654010,23654010,Retencion en la fuente por compras no declarantes 3.5%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23654015,23654015,Retencion en la fuente por compras bienes agricolas 1.5%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23654020,23654020,Retencion en la fuente por compra cafe 0.5%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23654025,23654025,Retencion en la fuente por compra combustibles 0.1%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23654030,23654030,Retencion en la fuente por adquisicion de vehiculos 1%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23654035,23654035,Retencion en la fuente por adquisicion de bienes raices comerciales 2.5%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23654040,23654040,Retencion en la fuente por ventas de activos fijos 1%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_23655005,23655005,Retencion en la fuente por consultorias servicios tecnicos y asistencia tecnica pagos al exterior 10%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_236575,236575,Autorretenciones,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_236705,236705,Impuesto a Las Ventas Retenido,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_236805,236805,Impuesto de Industria y Comercio Retenido,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
@@ -81,6 +109,7 @@ co_puc_2408101005,2408101005,Compra Anulada Iva 19%,FALSE,account.data_account_t
|
||||
co_puc_2408101010,2408101010,Compra Anulada Iva 5%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_2408101015,2408101015,Compra Anulada Iva 16%,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_240820,240820,Iva Descontable a Regimen Simplificado (Commun/Proporcional) - Solo Esta la DIAN lo Interpreta Utilizable,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_24082005,24082005,Iva retenido a regimen simplificado,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_241205,241205,Vigencia Fiscal Corriente,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_245605,245605,a Las Importaciones,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
co_puc_249505,249505,Otros,FALSE,account.data_account_type_current_liabilities,l10n_co_chart_template_generic
|
||||
@@ -107,8 +136,45 @@ co_puc_340515,340515,de Reservas,FALSE,account.data_account_type_equity,l10n_co_
|
||||
co_puc_340520,340520,de Resultados de Ejercicios Anteriores,FALSE,account.data_account_type_equity,l10n_co_chart_template_generic
|
||||
co_puc_360505,360505,Utilidad Del Ejercicio,FALSE,account.data_account_type_equity,l10n_co_chart_template_generic
|
||||
co_puc_370505,370505,Utilidades Acumuladas,FALSE,account.data_account_type_equity,l10n_co_chart_template_generic
|
||||
co_puc_415505,415505,Arrendamientos de Bienes Inmuebles,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413502,413502,Venta de vehículos automotores,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413504,413504,"Mantenimiento, reparación y lavado de vehículos automotores",FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413506,413506,"Venta de partes, piezas y accesorios de vehículos automotores",FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413508,413508,"Venta de combustibles sólidos, líquidos, gaseosos",FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413510,413510,"Venta de lubricantes, aditivos, llantas y lujos para automotores",FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413512,413512,Venta a cambio de retribución o por contrata,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413514,413514,"Venta de insumos, materias primas agropecuarias y flores",FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413516,413516,Venta de otros insumos y materias primas no agropecuarias,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413518,413518,Venta de animales vivos y cueros,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413520,413520,Venta de productos en almacenes no especializados,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413522,413522,Venta de productos agropecuarios,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413524,413524,"Venta de productos textiles, de vestir, de cuero y calzado",FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413526,413526,Venta de papel y cartón,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413528,413528,"Venta de libros, revistas, elementos de papelería, útiles y textos escolares",FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413530,413530,"Venta de juegos, juguetes y artículos deportivos",FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413532,413532,Venta de instrumentos quirúrgicos y ortopédicos,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413534,413534,Venta de artículos en relojerías y joyerías,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413536,413536,Venta de electrodomésticos y muebles,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413538,413538,"Venta de productos de aseo, farmacéuticos, medicinales, y artículos de tocador",FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413540,413540,"Venta de cubiertos, vajillas, cristalería, porcelanas, cerámicas y otros artículos de uso doméstico",FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413542,413542,"Venta de materiales de construcción, fontanería y calefacción",FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413544,413544,Venta de pinturas y lacas,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413546,413546,Venta de productos de vidrios y marquetería,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413548,413548,Venta de herramientas y artículos de ferretría,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413550,413550,Venta de químicos,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413552,413552,"Venta de productos intermedios, desperdicios y desechos",FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413554,413554,"Venta de maquinaria, equipo de oficina y programas de computador",FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413556,413556,Venta de artículos en cacharrerías y misceláneas,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413558,413558,Venta de instrumentos musicales,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413560,413560,Venta de artículos en casas de empeño y prenderías,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413562,413562,Venta de equipo fotográfico,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413564,413564,Venta de equipo óptico y de precisión,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413566,413566,Venta de empaques,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413568,413568,Venta de equipo profesional y científico,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413570,413570,"Venta de loterías, rifas, chance, apuestas y similares",FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413572,413572,Reparación de efectos personales y electrodomésticos,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413595,413595,Venta de Otros Productos,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_413595,413595,Venta de otros productos,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_415505,415505,Arrendamientos de Bienes Inmuebles,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_421005,421005,Intereses,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_421020,421020,Diferencia en Cambio,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
co_puc_421040,421040,Descuentos Comerciales Condicionados,FALSE,account.data_account_type_revenue,l10n_co_chart_template_generic
|
||||
|
||||
|
@@ -1,54 +1,54 @@
|
||||
id,amount,account_id:id,refund_account_id:id,name,type_tax_use,amount_type,children_tax_ids:id,chart_template_id:id
|
||||
l10n_co_tax_0,5.0,co_puc_2408100510,co_puc_2408101010,IVA Compra 5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_1,19.0,co_puc_2408100505,co_puc_2408101005,IVA Compra 19%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_2,16.0,co_puc_2408100515,co_puc_2408101015,IVA Descontable Compra 16% (2016 y Enero 2017),purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_3,0.0,co_puc_236505,co_puc_236505,RteFte Salarios y Pagos Laborales,None,fixed,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_4,16.0,,,IVA Devoluciones Ventas 16% 2016,sale,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_5,0.0,,,IVA Compra Importaciones,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_6,0.0,,,IVA Compra Excluido,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_7,0.0,,,IVA Compra Exento,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_8,19.0,co_puc_2408050505,co_puc_2408051005,IVA Ventas 19%,sale,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_9,5.0,co_puc_2408050510,co_puc_2408051010,IVA Ventas 5%,sale,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_10,0.0,,,IVA Excento,sale,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_11,0.0,,,IVA Excluido,sale,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_12,-2.85,co_puc_236705,co_puc_236705,RteIVA 15% sobre el 19% IVA,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_13,-0.75,co_puc_236705,co_puc_236705,RteIVA 15% sobre el 5% IVA,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_14,0.75,,,RteIVA 15% sobre el 5% IVA Contrapartida,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_15,2.85,,,RteIVA 15% sobre el 19% IVA Contrapartida,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_16,-0.1,,,RteFte Compra Combustibles 0.1%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_17,-0.5,,,RteFte Compra Cafe 0.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_18,-10.0,,,RteFte Comisiones Persona Natural 10%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_19,-2.5,,,RteFte Compras Declarantes 2.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_20,-10.0,,,"RteFte Consultorias, Servicios Tecnicos y Asistencia Tecnica Pagos al Exterior 10%",purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_21,-1.0,,,RteFte Adquisicion de Vehiculos 1%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_22,-1.0,,,RteFte Compras de Bienes Raices 1%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_23,-4.0,,,RteFte Servicios P Jurídicas y PN Declarantes 4%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_24,-1.0,,,RteFte Servicio Transporte Nacional Carga 1%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_25,-6.0,,,RteFte Servicios en General Personas Naturales No Declarantes Renta 6%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_26,-2.0,,,RteFte Servicio de Vigilancia y Aseo (Sobre AIU) 2%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_27,-3.5,,,RteFte Honorarios Servicios Licencias Software 3.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_28,-2.0,,,RteFte Contratos de Obra Inmuebles 2%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_29,-4.0,,,RteFte Arrendamientos Bienes Muebles 4%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_30,-3.5,,,RteFte Arrendamientos Bienes Inmuebles 3.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_31,-7.0,,,RteFte Rendimientos Financieros Generales 7%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_32,-10.0,,,RteFte Honorarios Persona Natural 10%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_33,-3.5,,,RteFte Honorarios Diseno Web y Consultoria Informatica 3.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_34,-2.5,,,RteFte Adquisicion de Bienes Raices Comerciales 2.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_35,-3.5,,,RteFte Compras No Declarantes 3.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_36,-11.0,,,RteFte Comisiones Persona Juridica 11%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_37,-1.5,,,RteFte Compras Bienes Agricolas 1.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_38,-3.5,,,RteFte Servicio Transporte Terrestre Nacional Pasajeros 3.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_39,-1.0,,,RteFte Servicio Temporales de Empleo (Sobre AIU) 1%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_40,-11.0,,,RteFte Honorarios Persona Juridica 11%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_41,-2.0,,,RteFte Servicio Integrales de Salud 2%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_42,-3.5,,,RteFte Servicio de Hoteles y Restaurantes 3.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_43,0.0,co_puc_236805,co_puc_236805,RteICA,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_44,-0.69,co_puc_236805,co_puc_236805,RteICA 0.69%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_45,-1.104,co_puc_236805,co_puc_236805,RteICA 1.104%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_46,-0.414,co_puc_236805,co_puc_236805,RteICA 0.414%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_47,-1.38,co_puc_236805,co_puc_236805,RteICA 1.38%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_48,-0.966,co_puc_236805,co_puc_236805,RteICA 0.966%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_49,16.0,,,IVA Compra 16% RC,purchase,group,"l10n_co_tax_1,l10n_co_tax_12",l10n_co_chart_template_generic
|
||||
l10n_co_tax_50,16.0,,,IVA Compra 16% RS,purchase,group,"l10n_co_tax_12,l10n_co_tax_15",l10n_co_chart_template_generic
|
||||
l10n_co_tax_51,5.0,,,IVA Compra 5% RS,purchase,group,"l10n_co_tax_13,l10n_co_tax_14",l10n_co_chart_template_generic
|
||||
l10n_co_tax_52,5.0,,,IVA Compra 5% RC,purchase,group,"l10n_co_tax_0,l10n_co_tax_13",l10n_co_chart_template_generic
|
||||
id,sequence,amount,account_id:id,refund_account_id:id,name,type_tax_use,amount_type,children_tax_ids:id,chart_template_id:id
|
||||
l10n_co_tax_0,1,5.0,co_puc_2408100510,co_puc_2408101010,IVA Compra 5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_1,0,19.0,co_puc_2408100505,co_puc_2408101005,IVA Compra 19%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_2,1,16.0,co_puc_2408100515,co_puc_2408101015,IVA Descontable Compra 16% (2016 y Enero 2017),purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_3,1,0.0,co_puc_236505,co_puc_236505,RteFte Salarios y Pagos Laborales,None,fixed,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_4,1,16.0,,,IVA Devoluciones Ventas 16% 2016,sale,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_5,1,0.0,,,IVA Compra Importaciones,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_6,1,0.0,,,IVA Compra Excluido,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_7,1,0.0,,,IVA Compra Exento,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_8,0,19.0,co_puc_2408050505,co_puc_2408051005,IVA Ventas 19%,sale,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_9,1,5.0,co_puc_2408050510,co_puc_2408051010,IVA Ventas 5%,sale,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_10,1,0.0,,,IVA Excento,sale,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_11,1,0.0,,,IVA Excluido,sale,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_12,1,-2.85,co_puc_236705,co_puc_236705,RteIVA 15% sobre el 19% IVA,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_13,1,-0.75,co_puc_236705,co_puc_236705,RteIVA 15% sobre el 5% IVA,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_14,1,0.75,co_puc_24082005,co_puc_24082005,RteIVA 15% sobre el 5% IVA Contrapartida,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_15,1,2.85,co_puc_24082005,co_puc_24082005,RteIVA 15% sobre el 19% IVA Contrapartida,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_16,1,-0.1,co_puc_23654025,co_puc_23654025,RteFte Compra Combustibles 0.1%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_17,1,-0.5,co_puc_23654020,co_puc_23654020,RteFte Compra Cafe 0.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_18,1,-10.0,co_puc_23652005,co_puc_23652005,RteFte Comisiones Persona Natural 10%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_19,1,-2.5,co_puc_23654005,co_puc_23654005,RteFte Compras Declarantes 2.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_20,1,-10.0,co_puc_23655005,co_puc_23655005,"RteFte Consultorias, Servicios Tecnicos y Asistencia Tecnica Pagos al Exterior 10%",purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_21,1,-1.0,co_puc_23654030,co_puc_23654030,RteFte Adquisicion de Vehiculos 1%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_22,1,-1.0,co_puc_23654040,co_puc_23654040,RteFte Compras de Bienes Raices 1%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_23,1,-4.0,co_puc_23652505,co_puc_23652505,RteFte Servicios P Jurídicas y PN Declarantes 4%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_24,1,-1.0,co_puc_23652515,co_puc_23652515,RteFte Servicio Transporte Nacional Carga 1%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_25,1,-6.0,co_puc_23652510,co_puc_23652510,RteFte Servicios en General Personas Naturales No Declarantes Renta 6%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_26,1,-2.0,co_puc_23652535,co_puc_23652535,RteFte Servicio de Vigilancia y Aseo (Sobre AIU) 2%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_27,1,-3.5,co_puc_23651515,co_puc_23651515,RteFte Honorarios Servicios Licencias Software 3.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_28,1,-2.0,co_puc_23652550,co_puc_23652550,RteFte Contratos de Obra Inmuebles 2%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_29,1,-4.0,co_puc_23653005,co_puc_23653005,RteFte Arrendamientos Bienes Muebles 4%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_30,1,-3.5,co_puc_23653010,co_puc_23653010,RteFte Arrendamientos Bienes Inmuebles 3.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_31,1,-7.0,co_puc_23653510,co_puc_23653510,RteFte Rendimientos Financieros Generales 7%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_32,1,-10.0,co_puc_23651505,co_puc_23651505,RteFte Honorarios Persona Natural 10%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_33,1,-3.5,co_puc_23651520,co_puc_23651520,RteFte Honorarios Diseno Web y Consultoria Informatica 3.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_34,1,-2.5,co_puc_23654035,co_puc_23654035,RteFte Adquisicion de Bienes Raices Comerciales 2.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_35,1,-3.5,co_puc_23654010,co_puc_23654010,RteFte Compras No Declarantes 3.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_36,1,-11.0,co_puc_23652010,co_puc_23652010,RteFte Comisiones Persona Juridica 11%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_37,1,-1.5,co_puc_23654015,co_puc_23654015,RteFte Compras Bienes Agricolas 1.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_38,1,-3.5,co_puc_23652520,co_puc_23652520,RteFte Servicio Transporte Terrestre Nacional Pasajeros 3.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_39,1,-1.0,co_puc_23652530,co_puc_23652530,RteFte Servicio Temporales de Empleo (Sobre AIU) 1%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_40,1,-11.0,co_puc_23651510,co_puc_23651510,RteFte Honorarios Persona Juridica 11%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_41,1,-2.0,co_puc_23652540,co_puc_23652540,RteFte Servicio Integrales de Salud 2%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_42,1,-3.5,co_puc_23652545,co_puc_23652545,RteFte Servicio de Hoteles y Restaurantes 3.5%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_43,1,0.0,co_puc_236805,co_puc_236805,RteICA,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_44,1,-0.69,co_puc_236805,co_puc_236805,RteICA 0.69%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_45,1,-1.104,co_puc_236805,co_puc_236805,RteICA 1.104%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_46,1,-0.414,co_puc_236805,co_puc_236805,RteICA 0.414%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_47,1,-1.38,co_puc_236805,co_puc_236805,RteICA 1.38%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_48,1,-0.966,co_puc_236805,co_puc_236805,RteICA 0.966%,purchase,percent,,l10n_co_chart_template_generic
|
||||
l10n_co_tax_49,1,19.0,,,IVA Compra 19% RC,purchase,group,"l10n_co_tax_1,l10n_co_tax_12",l10n_co_chart_template_generic
|
||||
l10n_co_tax_50,1,19.0,,,IVA Compra 19% RS,purchase,group,"l10n_co_tax_12,l10n_co_tax_15",l10n_co_chart_template_generic
|
||||
l10n_co_tax_51,1,5.0,,,IVA Compra 5% RS,purchase,group,"l10n_co_tax_13,l10n_co_tax_14",l10n_co_chart_template_generic
|
||||
l10n_co_tax_52,1,5.0,,,IVA Compra 5% RC,purchase,group,"l10n_co_tax_0,l10n_co_tax_13",l10n_co_chart_template_generic
|
||||
|
||||
|
@@ -12,7 +12,7 @@ class ResPartner(models.Model):
|
||||
('foreign_id_card', 'Cedula de Extranjeria'),
|
||||
('external_id', 'ID del Exterior')], string='Document Type',
|
||||
help='Indicates to what document the information in here belongs to.')
|
||||
l10n_co_verification_code = fields.Char(compute='_compute_verification_code', string='VC',
|
||||
l10n_co_verification_code = fields.Char(compute='_compute_verification_code', string='VC', # todo remove this field in master
|
||||
help='Redundancy check to verify the vat number has been typed in correctly.')
|
||||
|
||||
@api.depends('vat')
|
||||
@@ -40,3 +40,16 @@ class ResPartner(models.Model):
|
||||
partner.l10n_co_verification_code = 11 - number
|
||||
except ValueError:
|
||||
partner.l10n_co_verification_code = False
|
||||
|
||||
@api.constrains('vat', 'commercial_partner_country_id', 'l10n_co_document_type')
|
||||
def check_vat(self):
|
||||
# check_vat is implemented by base_vat which this localization
|
||||
# doesn't directly depend on. It is however automatically
|
||||
# installed for Colombia.
|
||||
if self.sudo().env.ref('base.module_base_vat').state == 'installed':
|
||||
# don't check Colombian partners unless they have RUT (= Colombian VAT) set as document type
|
||||
self = self.filtered(lambda partner: partner.country_id != self.env.ref('base.co') or\
|
||||
partner.l10n_co_document_type == 'rut')
|
||||
return super(ResPartner, self).check_vat()
|
||||
else:
|
||||
return True
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<field name="l10n_co_document_type"/>
|
||||
</field>
|
||||
<field name="vat" position="after">
|
||||
<field name="l10n_co_verification_code"/>
|
||||
<field name="l10n_co_verification_code" invisible="1"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -757,9 +757,10 @@ class Message(models.Model):
|
||||
'datas_fname': name,
|
||||
'res_model': 'mail.message',
|
||||
})
|
||||
attachment.generate_access_token()
|
||||
values['attachment_ids'].append((4, attachment.id))
|
||||
data_to_url[key] = '/web/image/%s' % attachment.id
|
||||
return '%s%s alt="%s"' % (data_to_url[key], match.group(3), name)
|
||||
data_to_url[key] = ['/web/image/%s?access_token=%s' % (attachment.id, attachment.access_token), name]
|
||||
return '%s%s alt="%s"' % (data_to_url[key][0], match.group(3), data_to_url[key][1])
|
||||
values['body'] = _image_dataurl.sub(base64_to_boundary, tools.ustr(values['body']))
|
||||
|
||||
# delegate creation of tracking after the create as sudo to avoid access rights issues
|
||||
|
||||
@@ -126,7 +126,7 @@ var MessagingMenu = Widget.extend({
|
||||
this.do_action({
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: resModel,
|
||||
views: [[false, 'form'], [false, 'kanban']],
|
||||
views: [[false, 'form']],
|
||||
res_id: resID
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -148,15 +148,12 @@ odoo.define('payment_stripe.stripe', function(require) {
|
||||
});
|
||||
}
|
||||
create_tx.done(function () {
|
||||
if (!_.contains(int_currencies, currency)) {
|
||||
amount = amount*100;
|
||||
}
|
||||
getStripeHandler().open({
|
||||
name: $("input[name='merchant']").val(),
|
||||
description: $("input[name='invoice_num']").val(),
|
||||
email: $("input[name='email']").val(),
|
||||
currency: currency,
|
||||
amount: amount,
|
||||
amount: _.contains(int_currencies, currency) ? amount : amount * 100,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,20 +11,21 @@ class PhoneValidationMixin(models.AbstractModel):
|
||||
def _phone_get_country(self):
|
||||
if 'country_id' in self:
|
||||
return self.country_id
|
||||
return self.env.user.company_id.country_id
|
||||
|
||||
def _phone_get_always_international(self):
|
||||
if 'company_id' in self:
|
||||
if 'company_id' in self and self.country_id:
|
||||
return self.company_id.phone_international_format == 'prefix'
|
||||
return self.env.user.company_id.phone_international_format == 'prefix'
|
||||
|
||||
def phone_format(self, number, country=None, company=None):
|
||||
country = country or self._phone_get_country()
|
||||
if not country:
|
||||
return number
|
||||
always_international = company.phone_international_format == 'prefix' if company else self._phone_get_always_international()
|
||||
return phone_validation.phone_format(
|
||||
number,
|
||||
country.code if country else None,
|
||||
country.phone_code if country else None,
|
||||
always_international=always_international,
|
||||
raise_exception=True
|
||||
raise_exception=False
|
||||
)
|
||||
|
||||
@@ -41,7 +41,7 @@ try:
|
||||
if raise_exception:
|
||||
raise
|
||||
else:
|
||||
_logger.warning(_('Unable to format %s:\n%s') % number, e)
|
||||
_logger.warning(_('Unable to format %s:\n%s'), number, e)
|
||||
return number
|
||||
if always_international or phone_nbr.country_code != country_phone_code:
|
||||
phone_fmt = phonenumbers.PhoneNumberFormat.INTERNATIONAL
|
||||
|
||||
@@ -2038,6 +2038,7 @@ var PaymentScreenWidget = ScreenWidget.extend({
|
||||
|
||||
invoiced.fail(function(error){
|
||||
self.invoicing = false;
|
||||
order.finalized = false;
|
||||
if (error.message === 'Missing Customer') {
|
||||
self.gui.show_popup('confirm',{
|
||||
'title': _t('Please select the Customer'),
|
||||
|
||||
@@ -909,22 +909,8 @@ class ProcurementRule(models.Model):
|
||||
for line in po.order_line:
|
||||
if line.product_id == product_id and line.product_uom == product_id.uom_po_id:
|
||||
if line._merge_in_existing_line(product_id, product_qty, product_uom, location_id, name, origin, values):
|
||||
procurement_uom_po_qty = product_uom._compute_quantity(product_qty, product_id.uom_po_id)
|
||||
seller = product_id._select_seller(
|
||||
partner_id=partner,
|
||||
quantity=line.product_qty + procurement_uom_po_qty,
|
||||
date=po.date_order and po.date_order[:10],
|
||||
uom_id=product_id.uom_po_id)
|
||||
|
||||
price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, line.product_id.supplier_taxes_id, line.taxes_id, values['company_id']) if seller else 0.0
|
||||
if price_unit and seller and po.currency_id and seller.currency_id != po.currency_id:
|
||||
price_unit = seller.currency_id.compute(price_unit, po.currency_id)
|
||||
|
||||
po_line = line.write({
|
||||
'product_qty': line.product_qty + procurement_uom_po_qty,
|
||||
'price_unit': price_unit,
|
||||
'move_dest_ids': [(4, x.id) for x in values.get('move_dest_ids', [])]
|
||||
})
|
||||
vals = self._update_purchase_order_line(product_id, product_qty, product_uom, values, line, partner)
|
||||
po_line = line.write(vals)
|
||||
break
|
||||
if not po_line:
|
||||
vals = self._prepare_purchase_order_line(product_id, product_qty, product_uom, values, po, supplier)
|
||||
@@ -948,6 +934,24 @@ class ProcurementRule(models.Model):
|
||||
|
||||
return schedule_date - relativedelta(days=int(seller.delay))
|
||||
|
||||
def _update_purchase_order_line(self, product_id, product_qty, product_uom, values, line, partner):
|
||||
procurement_uom_po_qty = product_uom._compute_quantity(product_qty, product_id.uom_po_id)
|
||||
seller = product_id._select_seller(
|
||||
partner_id=partner,
|
||||
quantity=line.product_qty + procurement_uom_po_qty,
|
||||
date=line.order_id.date_order and line.order_id.date_order[:10],
|
||||
uom_id=product_id.uom_po_id)
|
||||
|
||||
price_unit = self.env['account.tax']._fix_tax_included_price_company(seller.price, line.product_id.supplier_taxes_id, line.taxes_id, values['company_id']) if seller else 0.0
|
||||
if price_unit and seller and line.order_id.currency_id and seller.currency_id != line.order_id.currency_id:
|
||||
price_unit = seller.currency_id.compute(price_unit, line.order_id.currency_id)
|
||||
|
||||
return {
|
||||
'product_qty': line.product_qty + procurement_uom_po_qty,
|
||||
'price_unit': price_unit,
|
||||
'move_dest_ids': [(4, x.id) for x in values.get('move_dest_ids', [])]
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def _prepare_purchase_order_line(self, product_id, product_qty, product_uom, values, po, supplier):
|
||||
procurement_uom_po_qty = product_uom._compute_quantity(product_qty, product_id.uom_po_id)
|
||||
|
||||
@@ -57,7 +57,7 @@ class CustomerPortal(CustomerPortal):
|
||||
if access_token:
|
||||
values['no_breadcrumbs'] = True
|
||||
values['access_token'] = access_token
|
||||
values['portal_confirmation'] = request.env['ir.config_parameter'].sudo().get_param('sale.sale_portal_confirmation_options', default='none')
|
||||
values['portal_confirmation'] = order.get_portal_confirmation_action()
|
||||
|
||||
if kwargs.get('error'):
|
||||
values['error'] = kwargs['error']
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
<template id="portal_my_orders_payment" name="Payment on my orders" inherit_id="sale.portal_order_page">
|
||||
<xpath expr="//t[@name='portal_confirmation_sign']" position="after">
|
||||
<t t-if="not invoices and order.state in ('sent', 'sale') and portal_confirmation == 'pay' and order.payment_tx_id.state != 'done'" name="portal_confirmation_pay">
|
||||
<t t-if="order.amount_total and not invoices and order.state in ('sent', 'sale') and portal_confirmation == 'pay' and order.payment_tx_id.state != 'done'" name="portal_confirmation_pay">
|
||||
<a class="btn btn-primary ml8 pull-right" href="#portal_pay">
|
||||
<i class="fa fa-arrow-circle-right"/> Pay Now
|
||||
</a>
|
||||
@@ -49,7 +49,7 @@
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//div[@id='portal_sign']" position="after">
|
||||
<div class="row" t-if="not invoices and order.state in ('sent') and portal_confirmation == 'pay'" id="portal_pay">
|
||||
<div class="row" t-if="order.amount_total and not invoices and order.state in ('sent', 'sale') and portal_confirmation == 'pay' and order.payment_tx_id.state != 'done'" id="portal_pay">
|
||||
<div class="col-md-offset-3 col-md-6">
|
||||
<div t-if="pms or s2s_acquirers or form_acquirers" id="payment_method" class="text-left col-md-13">
|
||||
<h4 class="mb24">Pay with</h4>
|
||||
|
||||
@@ -70,6 +70,13 @@ class AccountAnalyticLine(models.Model):
|
||||
|
||||
@api.multi
|
||||
def _timesheet_compute_theorical_revenue(self):
|
||||
for timesheet in self:
|
||||
values = timesheet._timesheet_compute_theorical_revenue_values()
|
||||
timesheet.write(values)
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def _timesheet_compute_theorical_revenue_values(self):
|
||||
""" This method set the theorical revenue on the current timesheet lines.
|
||||
|
||||
If invoice on delivered quantity:
|
||||
@@ -82,53 +89,55 @@ class AccountAnalyticLine(models.Model):
|
||||
else:
|
||||
0
|
||||
"""
|
||||
for timesheet in self:
|
||||
# find the timesheet UoM
|
||||
timesheet_uom = timesheet.product_uom_id
|
||||
if not timesheet_uom: # fallback on default company timesheet UoM
|
||||
timesheet_uom = self.env.user.company_id.project_time_mode_id
|
||||
self.ensure_one()
|
||||
timesheet = self
|
||||
|
||||
# default values
|
||||
unit_amount = timesheet.unit_amount
|
||||
so_line = timesheet.so_line
|
||||
values = {
|
||||
'timesheet_revenue': 0.0,
|
||||
'timesheet_invoice_type': 'non_billable_project' if not timesheet.task_id else 'non_billable',
|
||||
}
|
||||
# set the revenue and billable type according to the product and the SO line
|
||||
if timesheet.task_id and so_line.product_id.type == 'service':
|
||||
# find the analytic account to convert revenue into its currency
|
||||
analytic_account = timesheet.account_id
|
||||
# convert the unit of mesure into hours
|
||||
sale_price_hour = so_line.product_uom._compute_price(so_line.price_unit, timesheet_uom)
|
||||
sale_price = so_line.currency_id.compute(sale_price_hour, analytic_account.currency_id) # amount from SO should be convert into analytic account currency
|
||||
# find the timesheet UoM
|
||||
timesheet_uom = timesheet.product_uom_id
|
||||
if not timesheet_uom: # fallback on default company timesheet UoM
|
||||
timesheet_uom = self.env.user.company_id.project_time_mode_id
|
||||
|
||||
# calculate the revenue on the timesheet
|
||||
if so_line.product_id.invoice_policy == 'delivery':
|
||||
values['timesheet_revenue'] = analytic_account.currency_id.round(unit_amount * sale_price * (1-(so_line.discount/100)))
|
||||
values['timesheet_invoice_type'] = 'billable_time' if so_line.product_id.service_type == 'timesheet' else 'billable_fixed'
|
||||
elif so_line.product_id.invoice_policy == 'order' and so_line.product_id.service_type == 'timesheet':
|
||||
quantity_hour = unit_amount
|
||||
if so_line.product_uom.category_id == timesheet_uom.category_id:
|
||||
quantity_hour = so_line.product_uom._compute_quantity(so_line.product_uom_qty, timesheet_uom)
|
||||
# compute the total revenue the SO since we are in fixed price
|
||||
total_revenue_so = analytic_account.currency_id.round(quantity_hour * sale_price * (1-(so_line.discount/100)))
|
||||
# compute the total revenue already existing (without the current timesheet line)
|
||||
domain = [('so_line', '=', so_line.id)]
|
||||
if timesheet.ids:
|
||||
domain += [('id', 'not in', timesheet.ids)]
|
||||
analytic_lines = timesheet.search(domain)
|
||||
total_revenue_invoiced = sum(analytic_lines.mapped('timesheet_revenue'))
|
||||
# compute (new) revenue of current timesheet line
|
||||
values['timesheet_revenue'] = min(
|
||||
analytic_account.currency_id.round(unit_amount * so_line.currency_id.compute(so_line.price_unit, analytic_account.currency_id) * (1-so_line.discount)),
|
||||
total_revenue_so - total_revenue_invoiced
|
||||
)
|
||||
values['timesheet_invoice_type'] = 'billable_fixed'
|
||||
# if the so line is already invoiced, and the delivered qty is still smaller than the ordered, then link the timesheet to the invoice
|
||||
if so_line.invoice_status == 'invoiced':
|
||||
values['timesheet_invoice_id'] = so_line.invoice_lines[0].invoice_id.id
|
||||
elif so_line.product_id.invoice_policy == 'order' and so_line.product_id.service_type != 'timesheet':
|
||||
values['timesheet_invoice_type'] = 'billable_fixed'
|
||||
timesheet.write(values)
|
||||
return True
|
||||
# default values
|
||||
unit_amount = timesheet.unit_amount
|
||||
so_line = timesheet.so_line
|
||||
values = {
|
||||
'timesheet_revenue': 0.0,
|
||||
'timesheet_invoice_type': 'non_billable_project' if not timesheet.task_id else 'non_billable',
|
||||
}
|
||||
# set the revenue and billable type according to the product and the SO line
|
||||
if timesheet.task_id and so_line.product_id.type == 'service':
|
||||
# find the analytic account to convert revenue into its currency
|
||||
analytic_account = timesheet.account_id
|
||||
# convert the unit of mesure into hours
|
||||
sale_price_hour = so_line.product_uom._compute_price(so_line.price_unit, timesheet_uom)
|
||||
sale_price = so_line.currency_id.compute(sale_price_hour, analytic_account.currency_id) # amount from SO should be convert into analytic account currency
|
||||
|
||||
# calculate the revenue on the timesheet
|
||||
if so_line.product_id.invoice_policy == 'delivery':
|
||||
values['timesheet_revenue'] = analytic_account.currency_id.round(unit_amount * sale_price * (1-(so_line.discount/100)))
|
||||
values['timesheet_invoice_type'] = 'billable_time' if so_line.product_id.service_type == 'timesheet' else 'billable_fixed'
|
||||
elif so_line.product_id.invoice_policy == 'order' and so_line.product_id.service_type == 'timesheet':
|
||||
quantity_hour = unit_amount
|
||||
if so_line.product_uom.category_id == timesheet_uom.category_id:
|
||||
quantity_hour = so_line.product_uom._compute_quantity(so_line.product_uom_qty, timesheet_uom)
|
||||
# compute the total revenue the SO since we are in fixed price
|
||||
total_revenue_so = analytic_account.currency_id.round(quantity_hour * sale_price * (1-(so_line.discount/100)))
|
||||
# compute the total revenue already existing (without the current timesheet line)
|
||||
domain = [('so_line', '=', so_line.id)]
|
||||
if timesheet.ids:
|
||||
domain += [('id', 'not in', timesheet.ids)]
|
||||
analytic_lines = timesheet.search(domain)
|
||||
total_revenue_invoiced = sum(analytic_lines.mapped('timesheet_revenue'))
|
||||
# compute (new) revenue of current timesheet line
|
||||
values['timesheet_revenue'] = min(
|
||||
analytic_account.currency_id.round(unit_amount * so_line.currency_id.compute(so_line.price_unit, analytic_account.currency_id) * (1-so_line.discount)),
|
||||
total_revenue_so - total_revenue_invoiced
|
||||
)
|
||||
values['timesheet_invoice_type'] = 'billable_fixed'
|
||||
# if the so line is already invoiced, and the delivered qty is still smaller than the ordered, then link the timesheet to the invoice
|
||||
if so_line.invoice_status == 'invoiced':
|
||||
values['timesheet_invoice_id'] = so_line.invoice_lines[0].invoice_id.id
|
||||
elif so_line.product_id.invoice_policy == 'order' and so_line.product_id.service_type != 'timesheet':
|
||||
values['timesheet_invoice_type'] = 'billable_fixed'
|
||||
|
||||
return values
|
||||
|
||||
@@ -14,6 +14,10 @@ The service is provided by the In App Purchase Odoo platform.
|
||||
'data': [
|
||||
'wizard/send_sms_views.xml',
|
||||
'views/res_partner_views.xml',
|
||||
'views/templates.xml',
|
||||
],
|
||||
'qweb': [
|
||||
'static/src/xml/sms_widget.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
odoo.define('sms.sms_widget', function (require) {
|
||||
"use strict";
|
||||
|
||||
var basicFields = require('web.basic_fields');
|
||||
var core = require('web.core');
|
||||
var fieldRegistry = require('web.field_registry');
|
||||
var framework = require('web.framework');
|
||||
|
||||
var FieldText = basicFields.FieldText;
|
||||
var QWeb = core.qweb;
|
||||
|
||||
/**
|
||||
* SmsWidget is a widget to display a textarea (the body) and a text representing
|
||||
* the number of SMS and the number of characters. This text is computed every
|
||||
* time the user changes the body.
|
||||
*/
|
||||
var SmsWidget = FieldText.extend({
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.nbrChar = 0;
|
||||
this.nbrSMS = 0;
|
||||
this.encoding = "GSM7";
|
||||
this.tagName = 'div';
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Compute the number of characters and sms
|
||||
* @private
|
||||
*/
|
||||
_compute: function () {
|
||||
var content = this._getValue();
|
||||
this.encoding = this._extractEncoding(content);
|
||||
this.nbrChar = content.length;
|
||||
this.nbrChar += (content.match(/\n/g) || []).length;
|
||||
this.nbrSMS = this._countSMS(this.nbrChar, this.encoding);
|
||||
this._renderSMS();
|
||||
},
|
||||
/**
|
||||
* Count the number of SMS of the content
|
||||
* @private
|
||||
* @returns {integer} Number of SMS
|
||||
*/
|
||||
_countSMS: function () {
|
||||
if (this.nbrChar === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (this.encoding === 'UNICODE') {
|
||||
if (this.nbrChar <= 70) {
|
||||
return 1;
|
||||
}
|
||||
return Math.ceil(this.nbrChar / 67);
|
||||
}
|
||||
if (this.nbrChar <= 160) {
|
||||
return 1;
|
||||
}
|
||||
return Math.ceil(this.nbrChar / 153);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_renderEdit: function () {
|
||||
this.$el.empty();
|
||||
this._prepareInput($('<textarea/>')).appendTo(this.$el);
|
||||
this.$el.append($(QWeb.render("sms.sms_count", {})));
|
||||
this._compute();
|
||||
},
|
||||
/**
|
||||
* Extract the encoding depending on the characters in the content
|
||||
* @private
|
||||
* @param {String} content Content of the SMS
|
||||
* @returns {String} Encoding of the content (GSM7 or UNICODE)
|
||||
*/
|
||||
_extractEncoding: function (content) {
|
||||
if (String(content).match(RegExp("^[@£$¥èéùìòÇ\\nØø\\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !\\\"#¤%&'()*+,-./0123456789:;<=>?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà]*$"))) {
|
||||
return 'GSM7';
|
||||
}
|
||||
return 'UNICODE';
|
||||
},
|
||||
/**
|
||||
* Render the number of characters, sms and the encoding.
|
||||
* @private
|
||||
*/
|
||||
_renderSMS: function () {
|
||||
this.$('.o_sms_count').text(this.nbrChar + ' / ' + this.nbrSMS + ' SMS (' + this.encoding + ') ');
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_onChange: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this._compute();
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_onInput: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this._compute();
|
||||
},
|
||||
});
|
||||
|
||||
fieldRegistry.add('sms_widget', SmsWidget);
|
||||
|
||||
return SmsWidget;
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
odoo.define('sms.sms_widget_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var FormView = require('web.FormView');
|
||||
var testUtils = require('web.test_utils');
|
||||
|
||||
var createView = testUtils.createView;
|
||||
|
||||
QUnit.module('fields', {
|
||||
beforeEach: function () {
|
||||
this.data = {
|
||||
partner: {
|
||||
fields: {
|
||||
message: {string: "message", type: "text"},
|
||||
},
|
||||
records: [{
|
||||
id: 1,
|
||||
message: "",
|
||||
}]
|
||||
},
|
||||
};
|
||||
}
|
||||
}, function () {
|
||||
|
||||
QUnit.module('SmsWidget');
|
||||
|
||||
QUnit.test('Sms widgets are correctly rendered', function (assert) {
|
||||
assert.expect(9);
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'partner',
|
||||
data: this.data,
|
||||
arch: '<form><sheet><field name="message" widget="sms_widget"/></sheet></form>',
|
||||
});
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
assert.ok(form.$('.o_sms_count').length, "Should have a sms counter");
|
||||
assert.strictEqual(form.$('.o_sms_count').text(), '0 / 0 SMS (GSM7) ', 'Should be "0 / 0 SMS (GSM7) " by default');
|
||||
// GSM-7
|
||||
form.$('.o_input').val("Hello from Odoo").trigger('input');
|
||||
assert.strictEqual(form.$('.o_sms_count').text(), '15 / 1 SMS (GSM7) ', 'Should be "15 / 1 SMS (GSM7) " for "Hello from Odoo"');
|
||||
// GSM-7 with \n => this one count as 2 characters
|
||||
form.$('.o_input').val("Hello from Odoo\n").trigger('input');
|
||||
assert.strictEqual(form.$('.o_sms_count').text(), '17 / 1 SMS (GSM7) ', 'Should be "17 / 1 SMS (GSM7) " for "Hello from Odoo\\n"');
|
||||
// Unicode => ê
|
||||
form.$('.o_input').val("Hêllo from Odoo").trigger('input');
|
||||
assert.strictEqual(form.$('.o_sms_count').text(), '15 / 1 SMS (UNICODE) ', 'Should be "15 / 1 SMS (UNICODE) " for "Hêllo from Odoo"');
|
||||
// GSM-7 with 160c
|
||||
var text = Array(161).join('a');
|
||||
form.$('.o_input').val(text).trigger('input');
|
||||
assert.strictEqual(form.$('.o_sms_count').text(), '160 / 1 SMS (GSM7) ', 'Should be "160 / 1 SMS (GSM7) " for 160 x "a"');
|
||||
// GSM-7 with 161c
|
||||
text = Array(162).join('a');
|
||||
form.$('.o_input').val(text).trigger('input');
|
||||
assert.strictEqual(form.$('.o_sms_count').text(), '161 / 2 SMS (GSM7) ', 'Should be "161 / 2 SMS (GSM7) " for 161 x "a"');
|
||||
// Unicode with 70c
|
||||
text = Array(71).join('ê');
|
||||
form.$('.o_input').val(text).trigger('input');
|
||||
assert.strictEqual(form.$('.o_sms_count').text(), '70 / 1 SMS (UNICODE) ', 'Should be "70 / 1 SMS (UNICODE) " for 70 x "ê"');
|
||||
// Unicode with 71c
|
||||
text = Array(72).join('ê');
|
||||
form.$('.o_input').val(text).trigger('input');
|
||||
assert.strictEqual(form.$('.o_sms_count').text(), '71 / 2 SMS (UNICODE) ', 'Should be "71 / 2 SMS (UNICODE) " for 71 x "ê"');
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates>
|
||||
<t t-name="sms.sms_count">
|
||||
<span class="pull-right"><span class="text-muted o_sms_count"></span> <a href="https://iap-services.odoo.com/iap/sms/pricing" class="fa fa-lg fa-info"/></span>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<template id="assets_backend" name="sms_assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/sms/static/src/js/sms_widget.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="qunit_suite" name="sms_widget_tests" inherit_id="web.qunit_suite">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/sms/static/src/tests/sms_widget_test.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -71,8 +71,7 @@ class SendSMS(models.TransientModel):
|
||||
phone_numbers = []
|
||||
no_phone_partners = []
|
||||
for partner in partners:
|
||||
default_field_name = 'mobile' if partner.mobile else 'phone'
|
||||
number = self._sms_sanitization(partner, self.env.context.get('field_name') or default_field_name)
|
||||
number = self._sms_sanitization(partner, self.env.context.get('field_name') or 'mobile')
|
||||
if number:
|
||||
phone_numbers.append(number)
|
||||
else:
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="recipients"/>
|
||||
<field name="message" widget="text"/>
|
||||
<field name="message" widget="sms_widget"/>
|
||||
</group>
|
||||
</sheet>
|
||||
<footer>
|
||||
|
||||
@@ -816,7 +816,8 @@ class StockMove(models.Model):
|
||||
|
||||
# Find a candidate move line to update or create a new one.
|
||||
for reserved_quant, quantity in quants:
|
||||
to_update = self.move_line_ids.filtered(lambda m: m.location_id.id == reserved_quant.location_id.id and m.lot_id.id == reserved_quant.lot_id.id and m.package_id.id == reserved_quant.package_id.id and m.owner_id.id == reserved_quant.owner_id.id)
|
||||
to_update = self.move_line_ids.filtered(lambda m: m.product_id.tracking != 'serial' and
|
||||
m.location_id.id == reserved_quant.location_id.id and m.lot_id.id == reserved_quant.lot_id.id and m.package_id.id == reserved_quant.package_id.id and m.owner_id.id == reserved_quant.owner_id.id)
|
||||
if to_update:
|
||||
to_update[0].with_context(bypass_reservation_update=True).product_uom_qty += self.product_id.uom_id._compute_quantity(quantity, self.product_uom, rounding_method='HALF-UP')
|
||||
else:
|
||||
|
||||
@@ -340,8 +340,13 @@ class StockMoveLine(models.Model):
|
||||
raise UserError(_('You can not delete product moves if the picking is done. You can only correct the done quantities.'))
|
||||
# Unlinking a move line should unreserve.
|
||||
if ml.product_id.type == 'product' and not ml.location_id.should_bypass_reservation() and not float_is_zero(ml.product_qty, precision_digits=precision):
|
||||
self.env['stock.quant']._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id,
|
||||
package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||
try:
|
||||
self.env['stock.quant']._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=ml.lot_id, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||
except UserError:
|
||||
if ml.lot_id:
|
||||
self.env['stock.quant']._update_reserved_quantity(ml.product_id, ml.location_id, -ml.product_qty, lot_id=False, package_id=ml.package_id, owner_id=ml.owner_id, strict=True)
|
||||
else:
|
||||
raise
|
||||
moves = self.mapped('move_id')
|
||||
res = super(StockMoveLine, self).unlink()
|
||||
if moves:
|
||||
|
||||
@@ -606,6 +606,55 @@ class StockMove(TransactionCase):
|
||||
quants = self.env['stock.quant']._gather(self.product2, self.stock_location)
|
||||
self.assertEqual(len(quants), 0)
|
||||
|
||||
def test_mixed_tracking_reservation_8(self):
|
||||
""" Send one product tracked by lot to a customer. In your stock, there are one tracked and
|
||||
one untracked quant. Reserve the move, then edit the lot to one not present in stock. The
|
||||
system will update the reservation and use the untracked quant. Now unreserve, no error
|
||||
should happen
|
||||
"""
|
||||
lot1 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot1',
|
||||
'product_id': self.product2.id,
|
||||
})
|
||||
|
||||
# at first, we only make the tracked quant available in stock to make sure this one is selected
|
||||
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 1, lot_id=lot1)
|
||||
|
||||
# creation
|
||||
move1 = self.env['stock.move'].create({
|
||||
'name': 'test_mixed_tracking_reservation_7',
|
||||
'location_id': self.stock_location.id,
|
||||
'location_dest_id': self.customer_location.id,
|
||||
'product_id': self.product2.id,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})
|
||||
move1._action_confirm()
|
||||
move1._action_assign()
|
||||
|
||||
self.assertEqual(move1.reserved_availability, 1.0)
|
||||
self.assertEqual(move1.move_line_ids.lot_id.id, lot1.id)
|
||||
|
||||
# change the lot_id to one not available in stock while an untracked quant is available
|
||||
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 1)
|
||||
lot2 = self.env['stock.production.lot'].create({
|
||||
'name': 'lot2',
|
||||
'product_id': self.product2.id,
|
||||
})
|
||||
move1.move_line_ids.lot_id = lot2
|
||||
self.assertEqual(move1.reserved_availability, 1.0)
|
||||
self.assertEqual(move1.move_line_ids.lot_id.id, lot2.id)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location, strict=True), 0.0)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location, lot_id=lot1, strict=True), 1.0)
|
||||
|
||||
# unreserve
|
||||
move1._do_unreserve()
|
||||
|
||||
self.assertEqual(move1.reserved_availability, 0.0)
|
||||
self.assertEqual(len(move1.move_line_ids), 0)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location, strict=True), 1.0)
|
||||
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.product2, self.stock_location, lot_id=lot1, strict=True), 1.0)
|
||||
|
||||
def test_putaway_1(self):
|
||||
""" Receive products from a supplier. Check that putaway rules are rightly applied on
|
||||
the receipt move line.
|
||||
@@ -756,6 +805,28 @@ class StockMove(TransactionCase):
|
||||
self.assertEqual(customer_quants.quantity, 30)
|
||||
self.assertEqual(customer_quants.reserved_quantity, 0)
|
||||
|
||||
def test_availability_5(self):
|
||||
""" Check that rerun action assign only create new stock move
|
||||
lines instead of adding quantity in existing one.
|
||||
"""
|
||||
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 2.0)
|
||||
# move from shelf1
|
||||
move = self.env['stock.move'].create({
|
||||
'name': 'test_edit_moveline_1',
|
||||
'location_id': self.stock_location.id,
|
||||
'location_dest_id': self.customer_location.id,
|
||||
'product_id': self.product2.id,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 4.0,
|
||||
})
|
||||
move._action_confirm()
|
||||
move._action_assign()
|
||||
|
||||
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 4.0)
|
||||
move._action_assign()
|
||||
|
||||
self.assertEqual(len(move.move_line_ids), 4.0)
|
||||
|
||||
def test_unreserve_1(self):
|
||||
""" Check that unreserving a stock move sets the products reserved as available and
|
||||
set the state back to confirmed.
|
||||
|
||||
@@ -111,6 +111,15 @@ class TestMessageValues(common.BaseFunctionalTest, common.MockEmails):
|
||||
self.assertNotIn('mail.test', msg.message_id)
|
||||
self.assertNotIn('-%d-' % self.alias_record.id, msg.message_id)
|
||||
|
||||
def test_mail_message_base64_image(self):
|
||||
msg = self.env['mail.message'].sudo(self.user_employee).create({
|
||||
'body': 'taratata <img src="data:image/png;base64,iV/+OkI=" width="2"> <img src="data:image/png;base64,iV/+OkI=" width="2">',
|
||||
})
|
||||
self.assertEqual(len(msg.attachment_ids), 1)
|
||||
body = '<p>taratata <img src="/web/image/%s?access_token=%s" alt="image0" width="2"> <img src="/web/image/%s?access_token=%s" alt="image0" width="2"></p>'
|
||||
body = body % (msg.attachment_ids[0].id, msg.attachment_ids[0].access_token, msg.attachment_ids[0].id, msg.attachment_ids[0].access_token)
|
||||
self.assertEqual(msg.body, body)
|
||||
|
||||
|
||||
class TestMessageAccess(common.BaseFunctionalTest, common.MockEmails):
|
||||
|
||||
|
||||
@@ -511,7 +511,7 @@ var BasicController = AbstractController.extend(FieldManagerMixin, {
|
||||
// TODO this will tell the renderer to rerender the widget that
|
||||
// asked for the discard but will unfortunately lose the click
|
||||
// made on another row if any
|
||||
self._confirmChange(self.handle, [ev.data.fieldName], ev)
|
||||
self._confirmChange(recordID, [ev.data.fieldName], ev)
|
||||
.always(ev.data.onSuccess);
|
||||
})
|
||||
.fail(ev.data.onFailure);
|
||||
|
||||
@@ -254,7 +254,10 @@ var BasicRenderer = AbstractRenderer.extend({
|
||||
if ('readonly' in modifiers && element.widget) {
|
||||
var mode = modifiers.readonly ? 'readonly' : modifiersData.baseMode;
|
||||
if (mode !== element.widget.mode) {
|
||||
self._rerenderFieldWidget(element.widget, record, mode);
|
||||
self._rerenderFieldWidget(element.widget, record, {
|
||||
keepBaseMode: true,
|
||||
mode: mode,
|
||||
});
|
||||
return; // Rerendering already applied the modifiers, no need to go further
|
||||
}
|
||||
}
|
||||
@@ -406,12 +409,25 @@ var BasicRenderer = AbstractRenderer.extend({
|
||||
* @param {Object} record
|
||||
* @param {jQuery|AbstractField} [element]
|
||||
* @param {Object} [options]
|
||||
* @param {Object} [options.callback] - the callback to call on registration
|
||||
* and on modifiers updates
|
||||
* @param {Object} [options.callback] the callback to call on registration
|
||||
* and on modifiers updates
|
||||
* @param {boolean} [options.keepBaseMode=false] this function registers the
|
||||
* 'baseMode' of the node; this is a field widget specific settings which
|
||||
* represents the generic mode of the widget, regardless of its modifiers
|
||||
* (the interesting case is the list view: all widgets are supposed to be
|
||||
* in the baseMode 'readonly', except the ones that are in the line that
|
||||
* is currently being edited).
|
||||
* With option 'keepBaseMode' set to true, the current baseMode of the
|
||||
* node isn't overridden (this is particularily useful when a field widget
|
||||
* is re-rendered because its readonly modifier changed, as in this case,
|
||||
* we don't want to change its base mode).
|
||||
* @param {string} [options.mode] the 'baseMode' of the node is set to this
|
||||
* value (if not given, it is set to this.mode, the mode of the renderer)
|
||||
* @returns {Object} for code efficiency, returns the last evaluated
|
||||
* modifiers for the given node and record.
|
||||
* modifiers for the given node and record.
|
||||
*/
|
||||
_registerModifiers: function (node, record, element, options) {
|
||||
options = options || {};
|
||||
// Check if we already registered the modifiers for the given node
|
||||
// If yes, this is simply an update of the related element
|
||||
// If not, check the modifiers to see if it needs registration
|
||||
@@ -428,12 +444,11 @@ var BasicRenderer = AbstractRenderer.extend({
|
||||
this.allModifiersData.push(modifiersData);
|
||||
}
|
||||
}
|
||||
// we register here the base mode of the node. This is a field widget
|
||||
// specific settings which represents the generic mode for the widget,
|
||||
// regardless of its modifiers. The interesting case is the list view:
|
||||
// all widgets are supposed to be in the baseMode 'readonly', except the
|
||||
// ones that are in the line that is currently being edited.
|
||||
modifiersData.baseMode = (options && options.mode) || this.mode;
|
||||
|
||||
// Compute node's base mode if necessary
|
||||
if (!options.keepBaseMode) {
|
||||
modifiersData.baseMode = options.mode || this.mode;
|
||||
}
|
||||
|
||||
// Evaluate if necessary
|
||||
if (!modifiersData.evaluatedModifiers[record.id]) {
|
||||
@@ -497,13 +512,16 @@ var BasicRenderer = AbstractRenderer.extend({
|
||||
* @private
|
||||
* @param {Object} node
|
||||
* @param {Object} record
|
||||
* @param {Object} [options]
|
||||
* @param {Object} [options] passed to @_registerModifiers
|
||||
* @param {string} [options.mode] either 'edit' or 'readonly' (defaults to
|
||||
* this.mode, the mode of the renderer)
|
||||
* @returns {jQueryElement}
|
||||
*/
|
||||
_renderFieldWidget: function (node, record, options) {
|
||||
options = options || {};
|
||||
var fieldName = node.attrs.name;
|
||||
// Register the node-associated modifiers
|
||||
var mode = options && options.mode || this.mode;
|
||||
var mode = options.mode || this.mode;
|
||||
var modifiers = this._registerModifiers(node, record, null, options);
|
||||
// Initialize and register the widget
|
||||
// Readonly status is known as the modifiers have just been registered
|
||||
@@ -545,7 +563,8 @@ var BasicRenderer = AbstractRenderer.extend({
|
||||
&& !element.widget.isSet()
|
||||
));
|
||||
},
|
||||
mode: mode
|
||||
keepBaseMode: !!options.keepBaseMode,
|
||||
mode: mode,
|
||||
});
|
||||
self._postProcessField(widget, node);
|
||||
});
|
||||
@@ -614,11 +633,11 @@ var BasicRenderer = AbstractRenderer.extend({
|
||||
* @private
|
||||
* @param {Widget} widget
|
||||
* @param {Object} record
|
||||
* @param {string} mode either 'readonly' or 'edit'
|
||||
* @param {Object} [options] options passed to @_renderFieldWidget
|
||||
*/
|
||||
_rerenderFieldWidget: function (widget, record, mode) {
|
||||
_rerenderFieldWidget: function (widget, record, options) {
|
||||
// Render the new field widget
|
||||
var $el = this._renderFieldWidget(widget.__node, record, {mode: mode});
|
||||
var $el = this._renderFieldWidget(widget.__node, record, options);
|
||||
widget.$el.replaceWith($el);
|
||||
|
||||
// Destroy the old widget and position the new one at the old one's
|
||||
|
||||
@@ -545,7 +545,6 @@ var PivotModel = AbstractModel.extend({
|
||||
var data = Array.prototype.slice.call(arguments);
|
||||
if (data[0][0].__count === 0) {
|
||||
self.data.has_data = false;
|
||||
return;
|
||||
}
|
||||
self._prepareData(data);
|
||||
});
|
||||
|
||||
@@ -630,7 +630,7 @@ QUnit.module('Views', {
|
||||
});
|
||||
|
||||
QUnit.test('readonly attrs on fields are re-evaluated on field change', function (assert) {
|
||||
assert.expect(3);
|
||||
assert.expect(4);
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
@@ -656,6 +656,9 @@ QUnit.module('Views', {
|
||||
form.$('.o_field_boolean input').click();
|
||||
assert.strictEqual(form.$('span[name="foo"]').length, 1,
|
||||
"the foo field widget should have been rerendered to now be readonly again");
|
||||
form.$('.o_field_boolean input').click();
|
||||
assert.strictEqual(form.$('input[name="foo"]').length, 1,
|
||||
"the foo field widget should have been rerendered to now be editable again");
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
@@ -1929,7 +1929,7 @@ QUnit.module('Views', {
|
||||
});
|
||||
|
||||
QUnit.test('readonly attrs on fields are re-evaluated on field change', function (assert) {
|
||||
assert.expect(7);
|
||||
assert.expect(9);
|
||||
|
||||
var list = createView({
|
||||
View: ListView,
|
||||
@@ -1963,9 +1963,14 @@ QUnit.module('Views', {
|
||||
assert.strictEqual(list.$('tbody td.o_readonly_modifier').length, 3,
|
||||
"the foo field widget parent cell should now be readonly again");
|
||||
|
||||
// Reswitch the cell to editable and save the row
|
||||
list.$('tbody tr:nth(0) td:nth(2) input').click();
|
||||
list.$('thead').click();
|
||||
assert.strictEqual(list.$('tbody tr:nth(0) td:nth(1) > input[name="foo"]').length, 1,
|
||||
"the foo field widget should have been rerendered as editable again");
|
||||
assert.strictEqual(list.$('tbody td.o_readonly_modifier').length, 2,
|
||||
"the foo field widget parent cell should not be readonly again");
|
||||
|
||||
// Click outside to leave edition mode
|
||||
list.$el.click();
|
||||
|
||||
assert.strictEqual(list.$('tbody td.o_readonly_modifier').length, 2,
|
||||
"there should be 2 readonly foo cells in readonly mode");
|
||||
@@ -2773,6 +2778,28 @@ QUnit.module('Views', {
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('pressing ESC discard the current line changes (with required)', function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
var list = createView({
|
||||
View: ListView,
|
||||
model: 'foo',
|
||||
data: this.data,
|
||||
arch: '<tree editable="top"><field name="foo" required="1"/></tree>',
|
||||
});
|
||||
|
||||
list.$buttons.find('.o_list_button_add').click();
|
||||
|
||||
list.$('input[name="foo"]').trigger({type: 'keydown', which: $.ui.keyCode.ESCAPE});
|
||||
assert.strictEqual(list.$('tr.o_data_row').length, 4,
|
||||
"should have 4 data row in list");
|
||||
assert.strictEqual(list.$('tr.o_data_row.o_selected_row').length, 0,
|
||||
"no rows should be selected");
|
||||
assert.ok(!list.$buttons.find('.o_list_button_save').is(':visible'),
|
||||
"should not have a visible save button");
|
||||
list.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('field with password attribute', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
odoo.define('web.pivot_tests', function (require) {
|
||||
"use strict";
|
||||
|
||||
var Context = require('web.Context');
|
||||
var core = require('web.core');
|
||||
var PivotView = require('web.PivotView');
|
||||
var testUtils = require('web.test_utils');
|
||||
@@ -739,7 +738,33 @@ QUnit.module('Views', {
|
||||
},
|
||||
});
|
||||
|
||||
$('.o_pivot_download').click();
|
||||
pivot.$buttons.find('.o_pivot_download').click();
|
||||
pivot.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('can download a file without data', function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
this.data.partner.records = [];
|
||||
|
||||
var pivot = createView({
|
||||
View: PivotView,
|
||||
model: "partner",
|
||||
data: this.data,
|
||||
arch: '<pivot>' +
|
||||
'<field name="date" interval="month" type="col"/>' +
|
||||
'<field name="foo" type="measure"/>' +
|
||||
'</pivot>',
|
||||
session: {
|
||||
get_file: function (args) {
|
||||
assert.strictEqual(args.url, '/web/pivot/export_xls',
|
||||
"should call get_file with correct parameters");
|
||||
args.complete();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
pivot.$buttons.find('.o_pivot_download').click();
|
||||
pivot.destroy();
|
||||
});
|
||||
|
||||
|
||||
@@ -146,6 +146,9 @@ class Web_Editor(http.Controller):
|
||||
# therefore we have to recover the files from the request object
|
||||
Attachments = request.env['ir.attachment'] # registry for the attachment table
|
||||
|
||||
res_model = kwargs.get('res_model', 'ir.ui.view')
|
||||
res_id = res_model != 'ir.ui.view' and kwargs.get('res_id') or None
|
||||
|
||||
uploads = []
|
||||
message = None
|
||||
if not upload: # no image provided, storing the link and the image name
|
||||
@@ -155,9 +158,11 @@ class Web_Editor(http.Controller):
|
||||
'type': 'url',
|
||||
'url': url,
|
||||
'public': True,
|
||||
'res_model': 'ir.ui.view',
|
||||
'res_id': res_id,
|
||||
'res_model': res_model,
|
||||
})
|
||||
uploads += attachment.read(['name', 'mimetype', 'checksum', 'url'])
|
||||
attachment.generate_access_token()
|
||||
uploads += attachment.read(['name', 'mimetype', 'checksum', 'url', 'res_id', 'res_model', 'access_token'])
|
||||
else: # images provided
|
||||
try:
|
||||
attachments = request.env['ir.attachment']
|
||||
@@ -180,10 +185,12 @@ class Web_Editor(http.Controller):
|
||||
'datas': base64.b64encode(data),
|
||||
'datas_fname': c_file.filename,
|
||||
'public': True,
|
||||
'res_model': 'ir.ui.view',
|
||||
'res_id': res_id,
|
||||
'res_model': res_model,
|
||||
})
|
||||
attachment.generate_access_token()
|
||||
attachments += attachment
|
||||
uploads += attachments.read(['name', 'mimetype', 'checksum', 'url'])
|
||||
uploads += attachments.read(['name', 'mimetype', 'checksum', 'url', 'res_id', 'res_model', 'access_token'])
|
||||
except Exception as e:
|
||||
logger.exception("Failed to upload image to attachment")
|
||||
message = pycompat.text_type(e)
|
||||
|
||||
@@ -118,6 +118,7 @@ var FieldTextHtmlSimple = basic_fields.DebouncedField.extend(TranslatableFieldMi
|
||||
*/
|
||||
_getValue: function () {
|
||||
if (this.nodeOptions['style-inline']) {
|
||||
transcoder.linkImgToAttachmentThumbnail(this.$content);
|
||||
transcoder.classToStyle(this.$content);
|
||||
transcoder.fontToImg(this.$content);
|
||||
}
|
||||
@@ -133,6 +134,8 @@ var FieldTextHtmlSimple = basic_fields.DebouncedField.extend(TranslatableFieldMi
|
||||
this.$textarea.summernote(this._getSummernoteConfig());
|
||||
this.$content = this.$('.note-editable:first');
|
||||
this.$content.html(this._textToHtml(this.value));
|
||||
this.$content.data('oe-id', this.recordData.res_id || this.res_id);
|
||||
this.$content.data('oe-model', this.recordData.model || this.model);
|
||||
// trigger a mouseup to refresh the editor toolbar
|
||||
this.$content.trigger('mouseup');
|
||||
if (this.nodeOptions['style-inline']) {
|
||||
|
||||
@@ -1080,8 +1080,12 @@ var SummernoteManager = Class.extend(mixins.EventDispatcherMixin, {
|
||||
return;
|
||||
}
|
||||
data.__alreadyDone = true;
|
||||
|
||||
var mediaDialog = new weWidgets.MediaDialog(this,
|
||||
data.options || {},
|
||||
_.extend({
|
||||
res_model: data.$editable.data('oe-model'),
|
||||
res_id: data.$editable.data('oe-id'),
|
||||
}, data.options),
|
||||
data.$editable,
|
||||
data.media
|
||||
);
|
||||
|
||||
@@ -872,7 +872,12 @@ registry.background = SnippetOption.extend({
|
||||
// Put fake image in the DOM, edit it and use it as background-image
|
||||
var $image = $('<img/>', {class: 'hidden', src: value}).appendTo(this.$target);
|
||||
|
||||
var _editor = new widget.MediaDialog(this, {}, null, $image[0]).open();
|
||||
var $editable = this.$target.closest('.o_editable');
|
||||
var options = {
|
||||
res_model: $editable.data('oe-model'),
|
||||
res_id: $editable.data('oe-id'),
|
||||
};
|
||||
var _editor = new widget.MediaDialog(this, options, null, $image[0]).open();
|
||||
_editor.opened(function () {
|
||||
_editor.$('[href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden');
|
||||
});
|
||||
|
||||
@@ -913,15 +913,21 @@ range.WrappedRange.prototype.deleteContents = function (towrite) {
|
||||
return this;
|
||||
}
|
||||
|
||||
var prevBP = dom.removeBetween(this.sc, this.so, this.ec, this.eo, towrite);
|
||||
var r;
|
||||
var image = this.isOnImg();
|
||||
if (image) {
|
||||
// If the range matches/is in an image, then the image is to be removed
|
||||
// and the cursor moved to its previous position
|
||||
var parentNode = image.parentNode;
|
||||
var index = _.indexOf(parentNode.childNodes, image);
|
||||
parentNode.removeChild(image);
|
||||
r = new range.WrappedRange(parentNode, index, parentNode, index);
|
||||
} else {
|
||||
r = dom.removeBetween(this.sc, this.so, this.ec, this.eo, towrite);
|
||||
}
|
||||
|
||||
$(dom.node(prevBP.sc)).trigger("click"); // trigger click to disable and reanable editor and image handler
|
||||
return new range.WrappedRange(
|
||||
prevBP.sc,
|
||||
prevBP.so,
|
||||
prevBP.ec,
|
||||
prevBP.eo
|
||||
);
|
||||
$(dom.node(r.sc)).trigger("click"); // trigger click to disable and reanable editor and image handler
|
||||
return new range.WrappedRange(r.sc, r.so, r.ec, r.eo);
|
||||
};
|
||||
range.WrappedRange.prototype.clean = function (mergeFilter, all) {
|
||||
var node = dom.node(this.sc === this.ec ? this.sc : this.commonAncestor());
|
||||
@@ -1258,7 +1264,7 @@ $.summernote.pluginEvents.visible = function (event, editor, layoutInfo) {
|
||||
};
|
||||
|
||||
function remove_table_content(r) {
|
||||
var nodes = dom.listBetween(r.sc, r.ec);
|
||||
var nodes = dom.listBetween(r.sc, r.ec, r.so, r.eo);
|
||||
if (dom.isText(r.sc)) {
|
||||
r.sc.textContent = r.sc.textContent.slice(0, r.so);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ function getMatchedCSSRules(a) {
|
||||
var sheets = document.styleSheets;
|
||||
for (i = sheets.length-1 ; i >= 0 ; i--) {
|
||||
var rules;
|
||||
if (sheets[i].rules) {
|
||||
if (sheets[i].hasOwnProperty('rules')) {
|
||||
rules = sheets[i].rules;
|
||||
} else {
|
||||
// try...catch because Firefox not able to enumerate
|
||||
@@ -259,10 +259,40 @@ function styleToClass($editable) {
|
||||
$c.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts css display for attachment link to real image.
|
||||
* Without this post process, the display depends on the css and the picture
|
||||
* does not appear when we use the html without css (to send by email for e.g.)
|
||||
*
|
||||
* @param {jQuery} $editable
|
||||
*/
|
||||
function attachmentThumbnailToLinkImg($editable) {
|
||||
$editable.find('a[href*="/web/content/"][data-mimetype]:empty').each(function () {
|
||||
var $link = $(this);
|
||||
var $img = $('<img/>')
|
||||
.attr('src', $link.css('background-image').replace(/(^url\(['"])|(['"]\)$)/g, ''))
|
||||
.css('height', Math.max(1, $link.height()) + 'px')
|
||||
.css('width', Math.max(1, $link.width()) + 'px');
|
||||
$link.append($img);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert attachmentThumbnailToLinkImg changes
|
||||
*
|
||||
* @see attachmentThumbnailToLinkImg
|
||||
* @param {jQuery} $editable
|
||||
*/
|
||||
function linkImgToAttachmentThumbnail($editable) {
|
||||
$editable.find('a[href*="/web/content/"][data-mimetype] > img').remove();
|
||||
}
|
||||
|
||||
return {
|
||||
fontToImg: fontToImg,
|
||||
imgToFont: imgToFont,
|
||||
classToStyle: classToStyle,
|
||||
styleToClass: styleToClass,
|
||||
attachmentThumbnailToLinkImg: attachmentThumbnailToLinkImg,
|
||||
linkImgToAttachmentThumbnail: linkImgToAttachmentThumbnail,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -53,6 +53,7 @@ snippet_editor.Class.include({
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
var $editable = $('#editable_area');
|
||||
transcoder.linkImgToAttachmentThumbnail($editable);
|
||||
transcoder.fontToImg($editable);
|
||||
transcoder.classToStyle($editable);
|
||||
|
||||
|
||||
@@ -120,7 +120,13 @@ var ImageDialog = Widget.extend({
|
||||
this._super.apply(this, arguments);
|
||||
this.options = options || {};
|
||||
this.accept = this.options.accept || this.options.document ? "*/*" : "image/*";
|
||||
this.domain = this.options.domain || ['|', ['mimetype', '=', false], ['mimetype', this.options.document ? 'not in' : 'in', ['image/gif', 'image/jpe', 'image/jpeg', 'image/jpg', 'image/gif', 'image/png']]];
|
||||
if (this.options.res_id) {
|
||||
this.domain = ['|',
|
||||
'&', ['res_model', '=', this.options.res_model], ['res_id', '=', this.options.res_id],
|
||||
['res_model', '=', 'ir.ui.view']];
|
||||
} else {
|
||||
this.domain = [['res_model', '=', 'ir.ui.view']];
|
||||
}
|
||||
this.parent = parent;
|
||||
this.old_media = media;
|
||||
this.media = media;
|
||||
@@ -171,6 +177,7 @@ var ImageDialog = Widget.extend({
|
||||
}
|
||||
},
|
||||
save: function () {
|
||||
var self = this;
|
||||
if (this.options.select_images) {
|
||||
return this.images;
|
||||
}
|
||||
@@ -181,34 +188,55 @@ var ImageDialog = Widget.extend({
|
||||
img = _.find(this.images, function (img) { return img.id === id;});
|
||||
}
|
||||
|
||||
var media;
|
||||
if (!img.is_document) {
|
||||
if (this.media.tagName !== "IMG" || !this.old_media) {
|
||||
this.add_class = "pull-left";
|
||||
this.style = {"width": "100%"};
|
||||
}
|
||||
if (this.media.tagName !== "IMG") {
|
||||
media = document.createElement('img');
|
||||
$(this.media).replaceWith(media);
|
||||
this.media = media;
|
||||
}
|
||||
this.media.setAttribute('src', img.src);
|
||||
} else {
|
||||
if (this.media.tagName !== "A") {
|
||||
$('.note-control-selection').hide();
|
||||
media = document.createElement('a');
|
||||
$(this.media).replaceWith(media);
|
||||
this.media = media;
|
||||
}
|
||||
this.media.setAttribute('href', '/web/content/' + img.id + '?unique=' + img.checksum + '&download=true');
|
||||
$(this.media).addClass('o_image').attr('title', img.name).attr('data-mimetype', img.mimetype);
|
||||
var def = $.when();
|
||||
if (!img.access_token) {
|
||||
def = this._rpc({
|
||||
model: 'ir.attachment',
|
||||
method: 'generate_access_token',
|
||||
args: [[img.id]]
|
||||
}).then(function (access_token) {
|
||||
img.access_token = access_token[0];
|
||||
});
|
||||
}
|
||||
|
||||
$(this.media).attr('alt', img.alt);
|
||||
var style = this.style;
|
||||
if (style) { $(this.media).css(style); }
|
||||
return def.then(function () {
|
||||
var media;
|
||||
if (!img.is_document) {
|
||||
if (img.access_token && self.options.res_model !== 'ir.ui.view') {
|
||||
img.src += _.str.sprintf('?access_token=%s', img.access_token);
|
||||
}
|
||||
if (self.media.tagName !== "IMG" || !self.old_media) {
|
||||
self.add_class = "pull-left";
|
||||
self.style = {"width": "100%"};
|
||||
}
|
||||
if (self.media.tagName !== "IMG") {
|
||||
media = document.createElement('img');
|
||||
$(self.media).replaceWith(media);
|
||||
self.media = media;
|
||||
}
|
||||
self.media.setAttribute('src', img.src);
|
||||
} else {
|
||||
if (self.media.tagName !== "A") {
|
||||
$('.note-control-selection').hide();
|
||||
media = document.createElement('a');
|
||||
$(self.media).replaceWith(media);
|
||||
self.media = media;
|
||||
}
|
||||
var href = '/web/content/' + img.id + '?';
|
||||
if (img.access_token && self.options.res_model !== 'ir.ui.view') {
|
||||
href += _.str.sprintf('access_token=%s&', img.access_token);
|
||||
}
|
||||
href += 'unique=' + img.checksum + '&download=true';
|
||||
self.media.setAttribute('href', href);
|
||||
$(self.media).addClass('o_image').attr('title', img.name).attr('data-mimetype', img.mimetype);
|
||||
}
|
||||
|
||||
return this.media;
|
||||
$(self.media).attr('alt', img.alt);
|
||||
var style = self.style;
|
||||
if (style) { $(self.media).css(style); }
|
||||
|
||||
return self.media;
|
||||
});
|
||||
},
|
||||
clear: function () {
|
||||
this.media.className = this.media.className.replace(/(^|\s+)((img(\s|$)|img-(?!circle|rounded|thumbnail))[^\s]*)/g, ' ');
|
||||
@@ -282,7 +310,7 @@ var ImageDialog = Widget.extend({
|
||||
}
|
||||
},
|
||||
fetch_existing: function (needle) {
|
||||
var domain = [['res_model', '=', 'ir.ui.view']].concat(this.domain);
|
||||
var domain = this.domain.concat(['|', ['mimetype', '=', false], ['mimetype', this.options.document ? 'not in' : 'in', ['image/gif', 'image/jpe', 'image/jpeg', 'image/jpg', 'image/gif', 'image/png']]]);
|
||||
if (needle && needle.length) {
|
||||
domain.push('|', ['datas_fname', 'ilike', needle], ['name', 'ilike', needle]);
|
||||
}
|
||||
@@ -292,7 +320,7 @@ var ImageDialog = Widget.extend({
|
||||
args: [],
|
||||
kwargs: {
|
||||
domain: domain,
|
||||
fields: ['name', 'mimetype', 'checksum', 'url', 'type'],
|
||||
fields: ['name', 'mimetype', 'checksum', 'url', 'type', 'res_id', 'res_model', 'access_token'],
|
||||
order: [{name: 'id', asc: false}],
|
||||
context: weContext.get(),
|
||||
}
|
||||
@@ -305,7 +333,7 @@ var ImageDialog = Widget.extend({
|
||||
return (r.url || r.id);
|
||||
});
|
||||
_.each(this.records, function (record) {
|
||||
record.src = record.url || _.str.sprintf('/web/image/%s/%s', record.id, record.name); // Name is added for SEO purposes
|
||||
record.src = record.url || _.str.sprintf('/web/image/%s/%s', record.id, encodeURI(record.name)); // Name is added for SEO purposes
|
||||
record.is_document = !(/gif|jpe|jpg|png/.test(record.mimetype));
|
||||
});
|
||||
this.display_attachments();
|
||||
@@ -1001,18 +1029,21 @@ var MediaDialog = Dialog.extend({
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
save: function () {
|
||||
var self = this;
|
||||
var args = arguments;
|
||||
var _super = this._super;
|
||||
if (this.options.select_images) {
|
||||
this.final_data = this.active.save();
|
||||
this._super.apply(this, arguments);
|
||||
return;
|
||||
return $.when(this.active.save()).then(function (data) {
|
||||
self.final_data = data;
|
||||
return _super.apply(self, args);
|
||||
});
|
||||
}
|
||||
if (this.rte) {
|
||||
this.range.select();
|
||||
this.rte.historyRecordUndo(this.media);
|
||||
}
|
||||
|
||||
var self = this;
|
||||
if (self.media) {
|
||||
if (this.media) {
|
||||
this.media.innerHTML = "";
|
||||
if (this.active !== this.imageDialog && this.active !== this.documentDialog) {
|
||||
this.imageDialog.clear();
|
||||
@@ -1029,27 +1060,27 @@ var MediaDialog = Dialog.extend({
|
||||
this.range.insertNode(this.media, true);
|
||||
this.active.media = this.media;
|
||||
}
|
||||
this.active.save();
|
||||
|
||||
if (this.active.add_class) {
|
||||
$(this.active.media).addClass(this.active.add_class);
|
||||
}
|
||||
var media = this.active.media;
|
||||
return $.when(this.active.save()).then(function () {
|
||||
if (self.active.add_class) {
|
||||
$(self.active.media).addClass(self.active.add_class);
|
||||
}
|
||||
var media = self.active.media;
|
||||
|
||||
this.final_data = [media, self.old_media];
|
||||
$(document.body).trigger("media-saved", this.final_data);
|
||||
$(self.old_media).trigger("save", this.final_data);
|
||||
$(this.final_data).trigger('input');
|
||||
self.final_data = [media, self.old_media];
|
||||
$(document.body).trigger("media-saved", self.final_data);
|
||||
$(self.old_media).trigger("save", self.final_data);
|
||||
$(self.final_data).trigger('input');
|
||||
|
||||
// Update editor bar after image edition (in case the image change to icon or other)
|
||||
_.defer(function () {
|
||||
if (!media.parentNode) return;
|
||||
range.createFromNode(media).select();
|
||||
click_event(media, "mousedown");
|
||||
click_event(media, "mouseup");
|
||||
// Update editor bar after image edition (in case the image change to icon or other)
|
||||
_.defer(function () {
|
||||
if (!media.parentNode) return;
|
||||
range.createFromNode(media).select();
|
||||
click_event(media, "mousedown");
|
||||
click_event(media, "mouseup");
|
||||
});
|
||||
return _super.apply(self, args);
|
||||
});
|
||||
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
searchTimer: null,
|
||||
search: function () {
|
||||
|
||||
@@ -86,6 +86,8 @@
|
||||
target="fileframe"
|
||||
class="form-inline">
|
||||
<input type="hidden" name="csrf_token" t-att-value="csrf_token"/>
|
||||
<input t-if="widget.options.res_id" type="hidden" name="res_id" t-att-value="widget.options.res_id"/>
|
||||
<input t-if="widget.options.res_model" type="hidden" name="res_model" t-att-value="widget.options.res_model"/>
|
||||
<div class="well">
|
||||
<div class="form-group pull-left">
|
||||
<input type="file" name="upload" t-att-accept="widget.accept" multiple="multiple" style="position: absolute; opacity: 0; width: 1px; height: 1px;"/>
|
||||
@@ -145,8 +147,9 @@
|
||||
<div class="existing-attachments">
|
||||
<div class="row mt16" t-as="row" t-foreach="rows">
|
||||
<div class="col-sm-2 o_existing_attachment_cell" t-as="attachment" t-foreach="row">
|
||||
<i class="fa fa-times o_existing_attachment_remove" t-att-data-id="attachment.id"/>
|
||||
<div class="o_attachment_border"><div t-att-data-src="attachment.src" t-att-data-url="attachment.url" t-att-alt="attachment.name" t-att-title="attachment.name" t-att-data-id="attachment.id" t-att-data-mimetype="attachment.mimetype" class="o_image"/></div>
|
||||
<i t-if="attachment.res_model === 'ir.ui.view'" class="fa fa-times o_existing_attachment_remove" title="This file is a public view attachment" t-att-data-id="attachment.id"/>
|
||||
<i t-else="" class="fa fa-times o_existing_attachment_remove" title="This file is attached to the current record" t-att-data-id="attachment.id"/>
|
||||
<div class="o_attachment_border" t-att-style="attachment.res_model === 'ir.ui.view' ? null : 'border: 1px solid #5cb85c;'"><div t-att-data-src="attachment.src" t-att-data-url="attachment.url" t-att-alt="attachment.name" t-att-title="attachment.name" t-att-data-id="attachment.id" t-att-data-mimetype="attachment.mimetype" class="o_image"/></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,7 @@ class QWeb(models.AbstractModel):
|
||||
def _get_asset(self, xmlid, options, css=True, js=True, debug=False, async=False, values=None):
|
||||
website = getattr(request, 'website', None) if request else None
|
||||
if website and website.cdn_activated:
|
||||
values = dict(values, url_for=website.get_cdn_url)
|
||||
values = dict(values or {}, url_for=website.get_cdn_url)
|
||||
return super(QWeb, self)._get_asset(xmlid, options, css, js, debug, async, values)
|
||||
|
||||
def _website_build_attribute(self, tagName, name, value, options, values):
|
||||
|
||||
@@ -200,8 +200,8 @@ class SaleOrder(models.Model):
|
||||
|
||||
def get_portal_confirmation_action(self):
|
||||
""" Template override default behavior of pay / sign chosen in sales settings """
|
||||
if self.template_id:
|
||||
return 'sign' if self.require_payment == 1 else 'pay'
|
||||
if self.require_payment is not None or self.require_payment is not False:
|
||||
return 'pay' if self.require_payment == 1 else 'sign'
|
||||
return super(SaleOrder, self).get_portal_confirmation_action()
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
Austria, 2018-02-06
|
||||
|
||||
WT-IO-IT GmbH agrees to the terms of the Odoo Corporate Contributor License
|
||||
Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Wolfgang Taferner wolfgang.taferner@wt-io-it.at https://github.com/wt-io-it
|
||||
|
||||
List of contributors:
|
||||
|
||||
Wolfgang Taferner wolfgang.taferner@wt-io-it.at https://github.com/wtaferner
|
||||
@@ -8,4 +8,4 @@ declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Rémi CAZENAVE remi@le-filament.com https://github.com/remi-filament
|
||||
Rémi CAZENAVE 30716308+remi-filament@users.noreply.github.com https://github.com/remi-filament
|
||||
|
||||
+2
-1
@@ -39,7 +39,8 @@ Step 2: Configure the odoo instance with your zone
|
||||
|
||||
In the Odoo back end, go to the :guilabel:`Website Settings`: menu, then
|
||||
activate the CDN support and copy/paste your zone URL in the
|
||||
:guilabel:`CDN Base URL` field.
|
||||
:guilabel:`CDN Base URL` field. This field is only visible and configurable if
|
||||
you have developer mode activated.
|
||||
|
||||
.. image:: cdn/odoo_cdn_base_url.png
|
||||
:class: img-responsive
|
||||
|
||||
@@ -8,6 +8,7 @@ import mimetypes
|
||||
import os
|
||||
import re
|
||||
from collections import defaultdict
|
||||
import uuid
|
||||
|
||||
from odoo import api, fields, models, tools, SUPERUSER_ID, _
|
||||
from odoo.exceptions import AccessError
|
||||
@@ -280,7 +281,7 @@ class IrAttachment(models.Model):
|
||||
public = fields.Boolean('Is public document')
|
||||
|
||||
# for external access
|
||||
access_token = fields.Char('Access Token')
|
||||
access_token = fields.Char('Access Token', groups="base.group_user")
|
||||
|
||||
# the field 'datas' is computed and may use the other fields below
|
||||
datas = fields.Binary(string='File Content', compute='_compute_datas', inverse='_inverse_datas')
|
||||
@@ -438,6 +439,14 @@ class IrAttachment(models.Model):
|
||||
self.browse().check('write', values=values)
|
||||
return super(IrAttachment, self).create(values)
|
||||
|
||||
@api.one
|
||||
def generate_access_token(self):
|
||||
if self.access_token:
|
||||
return self.access_token
|
||||
access_token = str(uuid.uuid4())
|
||||
self.write({'access_token': access_token})
|
||||
return access_token
|
||||
|
||||
@api.model
|
||||
def action_get(self):
|
||||
return self.env['ir.actions.act_window'].for_xml_id('base', 'action_attachment')
|
||||
|
||||
@@ -21,7 +21,7 @@ import odoo
|
||||
from odoo import api, http, models, tools, SUPERUSER_ID
|
||||
from odoo.exceptions import AccessDenied, AccessError
|
||||
from odoo.http import request, STATIC_CACHE, content_disposition
|
||||
from odoo.tools import pycompat
|
||||
from odoo.tools import pycompat, consteq
|
||||
from odoo.tools.mimetypes import guess_mimetype
|
||||
from odoo.modules.module import get_resource_path, get_module_path
|
||||
|
||||
@@ -269,6 +269,7 @@ class IrHttp(models.AbstractModel):
|
||||
:param str mimetype: mintype of the field (for headers)
|
||||
:param str default_mimetype: default mintype if no mintype found
|
||||
:param str access_token: optional token for unauthenticated access
|
||||
only available for ir.attachment
|
||||
:param Environment env: by default use request.env
|
||||
:returns: (status, headers, content)
|
||||
"""
|
||||
@@ -277,6 +278,10 @@ class IrHttp(models.AbstractModel):
|
||||
obj = None
|
||||
if xmlid:
|
||||
obj = env.ref(xmlid, False)
|
||||
elif id and model == 'ir.attachment' and access_token:
|
||||
obj = env[model].sudo().browse(int(id))
|
||||
if not consteq(obj.access_token, access_token):
|
||||
return (403, [], None)
|
||||
elif id and model in env.registry:
|
||||
obj = env[model].browse(int(id))
|
||||
|
||||
|
||||
@@ -144,8 +144,8 @@ class Company(models.Model):
|
||||
self.ensure_one()
|
||||
currency_id = self._get_user_currency()
|
||||
if country_id:
|
||||
currency_id = self.env['res.country'].browse(country_id).currency_id.id
|
||||
return {'value': {'currency_id': currency_id}}
|
||||
currency_id = self.env['res.country'].browse(country_id).currency_id
|
||||
return {'value': {'currency_id': currency_id.id}}
|
||||
|
||||
@api.onchange('country_id')
|
||||
def _onchange_country_id_wrapper(self):
|
||||
|
||||
Reference in New Issue
Block a user