Merge branch 'master-haresh-05072018' into 'master-patch-july-2018'
Master haresh 05072018 See merge request flectra-hq/flectra!93
This commit is contained in:
commit
548b771a48
@ -203,8 +203,12 @@ class PurchaseOrder(models.Model):
|
|||||||
self.payment_term_id = payment_term.id,
|
self.payment_term_id = payment_term.id,
|
||||||
self.company_id = requisition.company_id.id
|
self.company_id = requisition.company_id.id
|
||||||
self.currency_id = currency.id
|
self.currency_id = currency.id
|
||||||
self.origin = requisition.name
|
if not self.origin or requisition.name not in self.origin.split(', '):
|
||||||
self.partner_ref = requisition.name # to control vendor bill based on agreement reference
|
if self.origin:
|
||||||
|
if requisition.name:
|
||||||
|
self.origin = self.origin + ', ' + requisition.name
|
||||||
|
else:
|
||||||
|
self.origin = requisition.name
|
||||||
self.notes = requisition.description
|
self.notes = requisition.description
|
||||||
self.date_order = requisition.date_end or fields.Datetime.now()
|
self.date_order = requisition.date_end or fields.Datetime.now()
|
||||||
self.picking_type_id = requisition.picking_type_id.id
|
self.picking_type_id = requisition.picking_type_id.id
|
||||||
|
@ -18,6 +18,8 @@ from flectra.tools.float_utils import float_compare
|
|||||||
|
|
||||||
|
|
||||||
def float_to_time(float_hour):
|
def float_to_time(float_hour):
|
||||||
|
if float_hour == 24.0:
|
||||||
|
return datetime.time.max
|
||||||
return datetime.time(int(math.modf(float_hour)[1]), int(60 * math.modf(float_hour)[0]), 0)
|
return datetime.time(int(math.modf(float_hour)[1]), int(60 * math.modf(float_hour)[0]), 0)
|
||||||
|
|
||||||
|
|
||||||
@ -643,7 +645,9 @@ class ResourceCalendarAttendance(models.Model):
|
|||||||
], 'Day of Week', required=True, index=True, default='0')
|
], 'Day of Week', required=True, index=True, default='0')
|
||||||
date_from = fields.Date(string='Starting Date')
|
date_from = fields.Date(string='Starting Date')
|
||||||
date_to = fields.Date(string='End Date')
|
date_to = fields.Date(string='End Date')
|
||||||
hour_from = fields.Float(string='Work from', required=True, index=True, help="Start and End time of working.")
|
hour_from = fields.Float(string='Work from', required=True, index=True,
|
||||||
|
help="Start and End time of working.\n"
|
||||||
|
"A specific value of 24:00 is interpreted as 23:59:59.999999.")
|
||||||
hour_to = fields.Float(string='Work to', required=True)
|
hour_to = fields.Float(string='Work to', required=True)
|
||||||
calendar_id = fields.Many2one("resource.calendar", string="Resource's Calendar", required=True, ondelete='cascade')
|
calendar_id = fields.Many2one("resource.calendar", string="Resource's Calendar", required=True, ondelete='cascade')
|
||||||
|
|
||||||
|
@ -243,6 +243,21 @@ class ResourceWorkingHours(TestResourceCommon):
|
|||||||
compute_leaves=True)
|
compute_leaves=True)
|
||||||
self.assertEqual(res, 33.0)
|
self.assertEqual(res, 33.0)
|
||||||
|
|
||||||
|
def test_calendar_working_hours_24(self):
|
||||||
|
self.att_4 = self.env['resource.calendar.attendance'].create({
|
||||||
|
'name': 'Att4',
|
||||||
|
'calendar_id': self.calendar.id,
|
||||||
|
'dayofweek': '2',
|
||||||
|
'hour_from': 0,
|
||||||
|
'hour_to': 24
|
||||||
|
})
|
||||||
|
res = self.calendar.get_work_hours_count(
|
||||||
|
Datetime.from_string('2018-06-19 23:00:00'),
|
||||||
|
Datetime.from_string('2018-06-21 01:00:00'),
|
||||||
|
self.resource1_id,
|
||||||
|
compute_leaves=True)
|
||||||
|
self.assertAlmostEqual(res, 24.0)
|
||||||
|
|
||||||
def test_calendar_timezone(self):
|
def test_calendar_timezone(self):
|
||||||
# user in timezone UTC-9 asks for work hours
|
# user in timezone UTC-9 asks for work hours
|
||||||
# Limits: between 2013-02-19 10:00:00 and 2013-02-26 15:30:00 (User TZ)
|
# Limits: between 2013-02-19 10:00:00 and 2013-02-26 15:30:00 (User TZ)
|
||||||
|
@ -12,6 +12,8 @@ This module contains all the common features of Sales Management and eCommerce.
|
|||||||
""",
|
""",
|
||||||
'depends': ['sales_team', 'account', 'portal'],
|
'depends': ['sales_team', 'account', 'portal'],
|
||||||
'data': [
|
'data': [
|
||||||
|
'security/sale_security.xml',
|
||||||
|
'security/ir.model.access.csv',
|
||||||
'data/ir_sequence_data.xml',
|
'data/ir_sequence_data.xml',
|
||||||
'data/sale_data.xml',
|
'data/sale_data.xml',
|
||||||
'report/sale_report.xml',
|
'report/sale_report.xml',
|
||||||
@ -20,8 +22,6 @@ This module contains all the common features of Sales Management and eCommerce.
|
|||||||
'report/sale_report_templates.xml',
|
'report/sale_report_templates.xml',
|
||||||
'report/invoice_report_templates.xml',
|
'report/invoice_report_templates.xml',
|
||||||
'report/report_all_channels_sales_views.xml',
|
'report/report_all_channels_sales_views.xml',
|
||||||
'security/sale_security.xml',
|
|
||||||
'security/ir.model.access.csv',
|
|
||||||
'wizard/sale_make_invoice_advance_views.xml',
|
'wizard/sale_make_invoice_advance_views.xml',
|
||||||
'views/sale_views.xml',
|
'views/sale_views.xml',
|
||||||
'views/account_invoice_views.xml',
|
'views/account_invoice_views.xml',
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<!--Email template -->
|
<!--Email template -->
|
||||||
<record id="email_template_edi_sale" model="mail.template">
|
<record id="email_template_edi_sale" model="mail.template">
|
||||||
<field name="name">Sales Order - Send by Email</field>
|
<field name="name">Sales Order - Send by Email</field>
|
||||||
<field name="email_from">${(object.user_id.email and '%s <%s>' % (object.user_id.name, object.user_id.email) or '')|safe}</field>
|
<field name="email_from">${(object.user_id.email and '"%s" <%s>' % (object.user_id.name, object.user_id.email) or '')|safe}</field>
|
||||||
<field name="subject">${object.company_id.name} ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'} (Ref ${object.name or 'n/a' })</field>
|
<field name="subject">${object.company_id.name} ${object.state in ('draft', 'sent') and 'Quotation' or 'Order'} (Ref ${object.name or 'n/a' })</field>
|
||||||
<field name="partner_to">${object.partner_id.id}</field>
|
<field name="partner_to">${object.partner_id.id}</field>
|
||||||
<field name="model_id" ref="sale.model_sale_order"/>
|
<field name="model_id" ref="sale.model_sale_order"/>
|
||||||
@ -70,7 +70,7 @@ from ${object.company_id.name}.
|
|||||||
<field name="body_html"><![CDATA[<html>
|
<field name="body_html"><![CDATA[<html>
|
||||||
<head></head>
|
<head></head>
|
||||||
% set record = ctx.get('record')
|
% set record = ctx.get('record')
|
||||||
% set company = record and record.company_id or user.company_id
|
% set company = record and record.company_id or ctx.get('company')
|
||||||
<body style="margin: 0; padding: 0;">
|
<body style="margin: 0; padding: 0;">
|
||||||
<table border="0" width="100%" cellpadding="0" bgcolor="#ededed" style="padding: 20px; background-color: #ededed; border-collapse:separate;" summary="o_mail_notification">
|
<table border="0" width="100%" cellpadding="0" bgcolor="#ededed" style="padding: 20px; background-color: #ededed; border-collapse:separate;" summary="o_mail_notification">
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -39,6 +39,9 @@ class AccountInvoice(models.Model):
|
|||||||
def _onchange_delivery_address(self):
|
def _onchange_delivery_address(self):
|
||||||
addr = self.partner_id.address_get(['delivery'])
|
addr = self.partner_id.address_get(['delivery'])
|
||||||
self.partner_shipping_id = addr and addr.get('delivery')
|
self.partner_shipping_id = addr and addr.get('delivery')
|
||||||
|
if self.env.context.get('type', 'out_invoice') == 'out_invoice':
|
||||||
|
company = self.company_id or self.env.user.company_id
|
||||||
|
self.comment = company.with_context(lang=self.partner_id.lang).sale_note
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def action_invoice_paid(self):
|
def action_invoice_paid(self):
|
||||||
@ -96,15 +99,6 @@ class AccountInvoiceLine(models.Model):
|
|||||||
_inherit = 'account.invoice.line'
|
_inherit = 'account.invoice.line'
|
||||||
_order = 'invoice_id, layout_category_id, sequence, id'
|
_order = 'invoice_id, layout_category_id, sequence, id'
|
||||||
|
|
||||||
@api.depends('price_unit', 'discount', 'invoice_line_tax_ids', 'quantity',
|
|
||||||
'product_id', 'invoice_id.partner_id', 'invoice_id.currency_id', 'invoice_id.company_id',
|
|
||||||
'invoice_id.date_invoice')
|
|
||||||
def _compute_total_price(self):
|
|
||||||
for line in self:
|
|
||||||
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
|
|
||||||
taxes = line.invoice_line_tax_ids.compute_all(price, line.invoice_id.currency_id, line.quantity, product=line.product_id, partner=line.invoice_id.partner_id)
|
|
||||||
line.price_total = taxes['total_included']
|
|
||||||
|
|
||||||
sale_line_ids = fields.Many2many(
|
sale_line_ids = fields.Many2many(
|
||||||
'sale.order.line',
|
'sale.order.line',
|
||||||
'sale_order_line_invoice_rel',
|
'sale_order_line_invoice_rel',
|
||||||
@ -113,4 +107,3 @@ class AccountInvoiceLine(models.Model):
|
|||||||
layout_category_id = fields.Many2one('sale.layout_category', string='Section')
|
layout_category_id = fields.Many2one('sale.layout_category', string='Section')
|
||||||
layout_category_sequence = fields.Integer(string='Layout Sequence')
|
layout_category_sequence = fields.Integer(string='Layout Sequence')
|
||||||
# TODO: remove layout_category_sequence in master or make it work properly
|
# TODO: remove layout_category_sequence in master or make it work properly
|
||||||
price_total = fields.Monetary(compute='_compute_total_price', string='Total Amount', store=True)
|
|
||||||
|
@ -23,3 +23,6 @@ class ProductProduct(models.Model):
|
|||||||
return r
|
return r
|
||||||
|
|
||||||
sales_count = fields.Integer(compute='_sales_count', string='# Sales')
|
sales_count = fields.Integer(compute='_sales_count', string='# Sales')
|
||||||
|
|
||||||
|
def _get_invoice_policy(self):
|
||||||
|
return self.invoice_policy
|
||||||
|
@ -24,7 +24,7 @@ class ProductTemplate(models.Model):
|
|||||||
@api.depends('product_variant_ids.sales_count')
|
@api.depends('product_variant_ids.sales_count')
|
||||||
def _sales_count(self):
|
def _sales_count(self):
|
||||||
for product in self:
|
for product in self:
|
||||||
product.sales_count = sum([p.sales_count for p in product.product_variant_ids])
|
product.sales_count = sum([p.sales_count for p in product.with_context(active_test=False).product_variant_ids])
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def action_view_sales(self):
|
def action_view_sales(self):
|
||||||
|
@ -59,7 +59,7 @@ class SaleOrder(models.Model):
|
|||||||
# Search for invoices which have been 'cancelled' (filter_refund = 'modify' in
|
# Search for invoices which have been 'cancelled' (filter_refund = 'modify' in
|
||||||
# 'account.invoice.refund')
|
# 'account.invoice.refund')
|
||||||
# use like as origin may contains multiple references (e.g. 'SO01, SO02')
|
# use like as origin may contains multiple references (e.g. 'SO01, SO02')
|
||||||
refunds = invoice_ids.search([('origin', 'like', order.name)]).filtered(lambda r: r.type in ['out_invoice', 'out_refund'])
|
refunds = invoice_ids.search([('origin', 'like', order.name), ('company_id', '=', order.company_id.id), ('branch_id', '=', order.branch_id.id)]).filtered(lambda r: r.type in ['out_invoice', 'out_refund'])
|
||||||
invoice_ids |= refunds.filtered(lambda r: order.name in [origin.strip() for origin in r.origin.split(',')])
|
invoice_ids |= refunds.filtered(lambda r: order.name in [origin.strip() for origin in r.origin.split(',')])
|
||||||
# Search for refunds as well
|
# Search for refunds as well
|
||||||
refund_ids = self.env['account.invoice'].browse()
|
refund_ids = self.env['account.invoice'].browse()
|
||||||
@ -413,6 +413,9 @@ class SaleOrder(models.Model):
|
|||||||
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
|
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
|
||||||
invoices = {}
|
invoices = {}
|
||||||
references = {}
|
references = {}
|
||||||
|
invoices_origin = {}
|
||||||
|
invoices_name = {}
|
||||||
|
|
||||||
for order in self:
|
for order in self:
|
||||||
group_key = order.id if grouped else (order.partner_invoice_id.id, order.currency_id.id)
|
group_key = order.id if grouped else (order.partner_invoice_id.id, order.currency_id.id)
|
||||||
for line in order.order_line.sorted(key=lambda l: l.qty_to_invoice < 0):
|
for line in order.order_line.sorted(key=lambda l: l.qty_to_invoice < 0):
|
||||||
@ -423,13 +426,14 @@ class SaleOrder(models.Model):
|
|||||||
invoice = inv_obj.create(inv_data)
|
invoice = inv_obj.create(inv_data)
|
||||||
references[invoice] = order
|
references[invoice] = order
|
||||||
invoices[group_key] = invoice
|
invoices[group_key] = invoice
|
||||||
|
invoices_origin[group_key] = [invoice.origin]
|
||||||
|
invoices_name[group_key] = [invoice.name]
|
||||||
elif group_key in invoices:
|
elif group_key in invoices:
|
||||||
vals = {}
|
if order.name not in invoices_origin[group_key]:
|
||||||
if order.name not in invoices[group_key].origin.split(', '):
|
invoices_origin[group_key].append(order.name)
|
||||||
vals['origin'] = invoices[group_key].origin + ', ' + order.name
|
if order.client_order_ref and order.client_order_ref not in invoices_name[group_key]:
|
||||||
if order.client_order_ref and order.client_order_ref not in invoices[group_key].name.split(', ') and order.client_order_ref != invoices[group_key].name:
|
invoices_name[group_key].append(order.client_order_ref)
|
||||||
vals['name'] = invoices[group_key].name + ', ' + order.client_order_ref
|
|
||||||
invoices[group_key].write(vals)
|
|
||||||
if line.qty_to_invoice > 0:
|
if line.qty_to_invoice > 0:
|
||||||
line.invoice_line_create(invoices[group_key].id, line.qty_to_invoice)
|
line.invoice_line_create(invoices[group_key].id, line.qty_to_invoice)
|
||||||
elif line.qty_to_invoice < 0 and final:
|
elif line.qty_to_invoice < 0 and final:
|
||||||
@ -437,7 +441,11 @@ class SaleOrder(models.Model):
|
|||||||
|
|
||||||
if references.get(invoices.get(group_key)):
|
if references.get(invoices.get(group_key)):
|
||||||
if order not in references[invoices[group_key]]:
|
if order not in references[invoices[group_key]]:
|
||||||
references[invoice] = references[invoice] | order
|
references[invoices[group_key]] |= order
|
||||||
|
|
||||||
|
for group_key in invoices:
|
||||||
|
invoices[group_key].write({'name': ', '.join(invoices_name[group_key]),
|
||||||
|
'origin': ', '.join(invoices_origin[group_key])})
|
||||||
|
|
||||||
if not invoices:
|
if not invoices:
|
||||||
raise UserError(_('There is no invoiceable line.'))
|
raise UserError(_('There is no invoiceable line.'))
|
||||||
@ -1084,23 +1092,21 @@ class SaleOrderLine(models.Model):
|
|||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def name_get(self):
|
def name_get(self):
|
||||||
if self._context.get('sale_show_order_product_name'):
|
result = []
|
||||||
result = []
|
for so_line in self:
|
||||||
for so_line in self:
|
name = '%s - %s' % (so_line.order_id.name, so_line.name.split('\n')[0] or so_line.product_id.name)
|
||||||
name = '%s - %s' % (so_line.order_id.name, so_line.product_id.name)
|
if so_line.order_partner_id.ref:
|
||||||
result.append((so_line.id, name))
|
name = '%s (%s)' % (name, so_line.order_partner_id.ref)
|
||||||
return result
|
result.append((so_line.id, name))
|
||||||
return super(SaleOrderLine, self).name_get()
|
return result
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def name_search(self, name='', args=None, operator='ilike', limit=100):
|
def name_search(self, name='', args=None, operator='ilike', limit=100):
|
||||||
if self._context.get('sale_show_order_product_name'):
|
if operator in ('ilike', 'like', '=', '=like', '=ilike'):
|
||||||
if operator in ('ilike', 'like', '=', '=like', '=ilike'):
|
args = expression.AND([
|
||||||
domain = expression.AND([
|
args or [],
|
||||||
args or [],
|
['|', ('order_id.name', operator, name), ('name', operator, name)]
|
||||||
['|', ('order_id.name', operator, name), ('name', operator, name)]
|
])
|
||||||
])
|
|
||||||
return self.search(domain, limit=limit).name_get()
|
|
||||||
return super(SaleOrderLine, self).name_search(name, args, operator, limit)
|
return super(SaleOrderLine, self).name_search(name, args, operator, limit)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
@ -1202,7 +1208,7 @@ class SaleOrderLine(models.Model):
|
|||||||
on SO lines. This method is a hook: since analytic line are used for timesheet,
|
on SO lines. This method is a hook: since analytic line are used for timesheet,
|
||||||
expense, ... each use case should provide its part of the domain.
|
expense, ... each use case should provide its part of the domain.
|
||||||
"""
|
"""
|
||||||
return [('so_line', 'in', self.ids), ('amount', '<=', 0.0)]
|
return ['&', ('so_line', 'in', self.ids), ('amount', '<=', 0.0)]
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _analytic_compute_delivered_quantity(self):
|
def _analytic_compute_delivered_quantity(self):
|
||||||
@ -1242,3 +1248,7 @@ class SaleOrderLine(models.Model):
|
|||||||
so_line.write({'qty_delivered': qty})
|
so_line.write({'qty_delivered': qty})
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _is_delivery(self):
|
||||||
|
self.ensure_one()
|
||||||
|
return False
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<th class="text-right">Unit Price</th>
|
<th class="text-right">Unit Price</th>
|
||||||
<th t-if="display_discount" class="text-right" groups="sale.group_discount_per_so_line">Disc.(%)</th>
|
<th t-if="display_discount" class="text-right" groups="sale.group_discount_per_so_line">Disc.(%)</th>
|
||||||
<th class="text-right">Taxes</th>
|
<th class="text-right">Taxes</th>
|
||||||
<th class="text-right">Price</th>
|
<th class="text-right">Amount</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="invoice_tbody">
|
<tbody class="invoice_tbody">
|
||||||
@ -53,7 +53,7 @@
|
|||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<span t-esc="', '.join(map(lambda x: x.description or x.name, l.invoice_line_tax_ids))"/>
|
<span t-esc="', '.join(map(lambda x: x.description or x.name, l.invoice_line_tax_ids))"/>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right" groups="sale.group_show_price_subtotal">
|
<td class="text-right">
|
||||||
<span t-field="l.price_subtotal"
|
<span t-field="l.price_subtotal"
|
||||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||||
</td>
|
</td>
|
||||||
|
@ -57,6 +57,7 @@ class PosSaleReport(models.Model):
|
|||||||
(cr.date_end IS NULL OR cr.date_end > COALESCE(so.date_order, now())))
|
(cr.date_end IS NULL OR cr.date_end > COALESCE(so.date_order, now())))
|
||||||
LEFT JOIN product_uom u on (u.id=sol.product_uom)
|
LEFT JOIN product_uom u on (u.id=sol.product_uom)
|
||||||
LEFT JOIN product_uom u2 on (u2.id=pt.uom_id)
|
LEFT JOIN product_uom u2 on (u2.id=pt.uom_id)
|
||||||
|
WHERE so.state != 'cancel'
|
||||||
""" % self.env['res.currency']._select_companies_rates()
|
""" % self.env['res.currency']._select_companies_rates()
|
||||||
return so_str
|
return so_str
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<field name="name">report.all.channels.sales.pivot</field>
|
<field name="name">report.all.channels.sales.pivot</field>
|
||||||
<field name="model">report.all.channels.sales</field>
|
<field name="model">report.all.channels.sales</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<pivot string="All Channels Sales Orders Analysis">
|
<pivot string="All Channels Sales Orders Analysis" disable_linking="True">
|
||||||
<field name="name" type="row"/>
|
<field name="name" type="row"/>
|
||||||
<field name="price_total" string="Total Price" type="measure"/>
|
<field name="price_total" string="Total Price" type="measure"/>
|
||||||
</pivot>
|
</pivot>
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
string="PRO-FORMA Invoice"
|
string="PRO-FORMA Invoice"
|
||||||
model="sale.order"
|
model="sale.order"
|
||||||
report_type="qweb-pdf"
|
report_type="qweb-pdf"
|
||||||
|
groups="sale.group_proforma_sales"
|
||||||
file="sale.report_saleorder_pro_forma"
|
file="sale.report_saleorder_pro_forma"
|
||||||
name="sale.report_saleorder_pro_forma"
|
name="sale.report_saleorder_pro_forma"
|
||||||
print_report_name="'PRO-FORMA - %s' % (object.name)"
|
print_report_name="'PRO-FORMA - %s' % (object.name)"
|
||||||
|
@ -60,6 +60,11 @@
|
|||||||
<strong>Payment Terms:</strong>
|
<strong>Payment Terms:</strong>
|
||||||
<p t-field="doc.payment_term_id"/>
|
<p t-field="doc.payment_term_id"/>
|
||||||
</div>
|
</div>
|
||||||
|
<div t-if="doc.validity_date and doc.state in ['draft', 'sent']" class="col-xs-3">
|
||||||
|
<strong>Expiration Date:</strong>
|
||||||
|
<p t-field="doc.validity_date"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div name="branch" t-if="doc.branch_id" class="col-xs-3" groups="base_branch_company.group_multi_branch">
|
<div name="branch" t-if="doc.branch_id" class="col-xs-3" groups="base_branch_company.group_multi_branch">
|
||||||
<strong>Branch:</strong>
|
<strong>Branch:</strong>
|
||||||
<p t-field="doc.branch_id"/>
|
<p t-field="doc.branch_id"/>
|
||||||
|
@ -158,9 +158,6 @@
|
|||||||
<field name="portal_confirmation_options" class="o_light_label" widget="radio"
|
<field name="portal_confirmation_options" class="o_light_label" widget="radio"
|
||||||
attrs="{'required': [('portal_confirmation', '=', True)]}"/>
|
attrs="{'required': [('portal_confirmation', '=', True)]}"/>
|
||||||
</div>
|
</div>
|
||||||
<div attrs="{'invisible': [('portal_confirmation_options', '!=', 'pay')]}">
|
|
||||||
<button name='%(payment.action_payment_acquirer)d' icon="fa-arrow-right" type="action" string="Payment Acquirers" class="btn-link"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -269,7 +266,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<h2>Invoicing</h2>
|
<h2>Invoicing</h2>
|
||||||
<div class="row mt16 o_settings_container">
|
<div class="row mt16 o_settings_container">
|
||||||
<div class="col-xs-12 col-md-6 o_setting_box" title="This default value is applied to any new product created. This can be changed in the product detail form.">
|
<div class="col-xs-12 col-md-6 o_setting_box hidden" title="This default value is applied to any new product created. This can be changed in the product detail form.">
|
||||||
<div class="o_setting_right_pane">
|
<div class="o_setting_right_pane">
|
||||||
<label for="default_invoice_policy"/>
|
<label for="default_invoice_policy"/>
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
|
@ -12,8 +12,6 @@
|
|||||||
Create a Quotation, the first step of a new sale.
|
Create a Quotation, the first step of a new sale.
|
||||||
</p><p>
|
</p><p>
|
||||||
Your next actions should flow efficiently: confirm the Quotation to a Sales Order, then create the Invoice and collect the Payment.
|
Your next actions should flow efficiently: confirm the Quotation to a Sales Order, then create the Invoice and collect the Payment.
|
||||||
</p><p>
|
|
||||||
Note that once a Quotation becomes a Sales Order, it will be moved from the Quotations list to the Sales Order list.
|
|
||||||
</p>
|
</p>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
@ -148,7 +148,7 @@
|
|||||||
<span class="label label-default"><i class="fa fa-fw fa-remove"/> Cancelled</span>
|
<span class="label label-default"><i class="fa fa-fw fa-remove"/> Cancelled</span>
|
||||||
</t>
|
</t>
|
||||||
<t t-if="order.state == 'done'">
|
<t t-if="order.state == 'done'">
|
||||||
<span class="label label-default"><i class="fa fa-fw fa-remove"/> Done</span>
|
<span class="label label-success"><i class="fa fa-fw fa-check" /> Done</span>
|
||||||
</t>
|
</t>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
|
@ -586,9 +586,6 @@
|
|||||||
</p><p>
|
</p><p>
|
||||||
Your next actions should flow efficiently: confirm the Quotation
|
Your next actions should flow efficiently: confirm the Quotation
|
||||||
to a Sales Order, then create the Invoice and collect the Payment.
|
to a Sales Order, then create the Invoice and collect the Payment.
|
||||||
</p><p>
|
|
||||||
Note that once a Quotation becomes a Sales Order, it will be moved
|
|
||||||
from the Quotations list to the Sales Order list.
|
|
||||||
</p>
|
</p>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@ -839,8 +836,6 @@
|
|||||||
Create a Quotation, the first step of a new sale.
|
Create a Quotation, the first step of a new sale.
|
||||||
</p><p>
|
</p><p>
|
||||||
Your next actions should flow efficiently: confirm the Quotation to a Sales Order, then create the Invoice and collect the Payment.
|
Your next actions should flow efficiently: confirm the Quotation to a Sales Order, then create the Invoice and collect the Payment.
|
||||||
</p><p>
|
|
||||||
Note that once a Quotation becomes a Sales Order, it will be moved from the Quotations list to the Sales Order list.
|
|
||||||
</p>
|
</p>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@ -880,9 +875,6 @@
|
|||||||
</p><p>
|
</p><p>
|
||||||
Your next actions should flow efficiently: confirm the Quotation
|
Your next actions should flow efficiently: confirm the Quotation
|
||||||
to a Sales Order, then create the Invoice and collect the Payment.
|
to a Sales Order, then create the Invoice and collect the Payment.
|
||||||
</p><p>
|
|
||||||
Note that once a Quotation becomes a Sales Order, it will be moved
|
|
||||||
from the Quotations list to the Sales Order list.
|
|
||||||
</p>
|
</p>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
@ -65,7 +65,7 @@ class SaleAdvancePaymentInv(models.TransientModel):
|
|||||||
|
|
||||||
account_id = False
|
account_id = False
|
||||||
if self.product_id.id:
|
if self.product_id.id:
|
||||||
account_id = self.product_id.property_account_income_id.id
|
account_id = self.product_id.property_account_income_id.id or self.product_id.categ_id.property_account_income_categ_id.id
|
||||||
if not account_id:
|
if not account_id:
|
||||||
inc_acc = ir_property_obj.get('property_account_income_categ_id', 'product.category')
|
inc_acc = ir_property_obj.get('property_account_income_categ_id', 'product.category')
|
||||||
account_id = order.fiscal_position_id.map_account(inc_acc).id if inc_acc else False
|
account_id = order.fiscal_position_id.map_account(inc_acc).id if inc_acc else False
|
||||||
@ -179,4 +179,5 @@ class SaleAdvancePaymentInv(models.TransientModel):
|
|||||||
'invoice_policy': 'order',
|
'invoice_policy': 'order',
|
||||||
'property_account_income_id': self.deposit_account_id.id,
|
'property_account_income_id': self.deposit_account_id.id,
|
||||||
'taxes_id': [(6, 0, self.deposit_taxes_id.ids)],
|
'taxes_id': [(6, 0, self.deposit_taxes_id.ids)],
|
||||||
|
'company_id': False,
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,8 @@ tour.register('sale_tour', {
|
|||||||
in_modal: false,
|
in_modal: false,
|
||||||
run: function (actions) {
|
run: function (actions) {
|
||||||
actions.auto();
|
actions.auto();
|
||||||
|
// There might be a modal because of the view:
|
||||||
|
// sale.order.form.editable.list, enabled by some groups
|
||||||
if ($(".modal-footer .btn-primary").length) {
|
if ($(".modal-footer .btn-primary").length) {
|
||||||
actions.auto(".modal-footer .btn-primary");
|
actions.auto(".modal-footer .btn-primary");
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from functools import partial
|
|
||||||
import flectra
|
|
||||||
from flectra import api, SUPERUSER_ID
|
|
||||||
|
|
||||||
from . import models # noqa
|
from . import models # noqa
|
||||||
from . import report # noqa
|
from . import report # noqa
|
||||||
|
|
||||||
|
|
||||||
def uninstall_hook(cr, registry):
|
|
||||||
def recreate_view(dbname):
|
|
||||||
db_registry = openerp.modules.registry.Registry.new(dbname)
|
|
||||||
with api.Environment.manage(), db_registry.cursor() as cr:
|
|
||||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
|
||||||
if 'sale.report' in env:
|
|
||||||
env['sale.report'].init()
|
|
||||||
|
|
||||||
cr.after("commit", partial(recreate_view, cr.dbname))
|
|
||||||
|
@ -16,5 +16,4 @@ Price and Cost Price.
|
|||||||
'depends':['sale_management'],
|
'depends':['sale_management'],
|
||||||
'demo':['data/sale_margin_demo.xml'],
|
'demo':['data/sale_margin_demo.xml'],
|
||||||
'data':['security/ir.model.access.csv','views/sale_margin_view.xml'],
|
'data':['security/ir.model.access.csv','views/sale_margin_view.xml'],
|
||||||
'uninstall_hook': "uninstall_hook",
|
|
||||||
}
|
}
|
||||||
|
@ -62,8 +62,7 @@ class AccountInvoiceLine(models.Model):
|
|||||||
qty_done = sum([x.uom_id._compute_quantity(x.quantity, x.product_id.uom_id) for x in s_line.invoice_lines if x.invoice_id.state in ('open', 'paid')])
|
qty_done = sum([x.uom_id._compute_quantity(x.quantity, x.product_id.uom_id) for x in s_line.invoice_lines if x.invoice_id.state in ('open', 'paid')])
|
||||||
quantity = self.uom_id._compute_quantity(self.quantity, self.product_id.uom_id)
|
quantity = self.uom_id._compute_quantity(self.quantity, self.product_id.uom_id)
|
||||||
# Put moves in fixed order by date executed
|
# Put moves in fixed order by date executed
|
||||||
moves = s_line.move_ids
|
moves = s_line.move_ids.sorted(lambda x: x.date)
|
||||||
moves.sorted(lambda x: x.date)
|
|
||||||
# Go through all the moves and do nothing until you get to qty_done
|
# Go through all the moves and do nothing until you get to qty_done
|
||||||
# Beyond qty_done we need to calculate the average of the price_unit
|
# Beyond qty_done we need to calculate the average of the price_unit
|
||||||
# on the moves we encounter.
|
# on the moves we encounter.
|
||||||
|
@ -33,7 +33,7 @@ class SaleOrder(models.Model):
|
|||||||
for order in self:
|
for order in self:
|
||||||
dates_list = []
|
dates_list = []
|
||||||
order_datetime = fields.Datetime.from_string(order.date_order)
|
order_datetime = fields.Datetime.from_string(order.date_order)
|
||||||
for line in order.order_line.filtered(lambda x: x.state != 'cancel'):
|
for line in order.order_line.filtered(lambda x: x.state != 'cancel' and not x._is_delivery()):
|
||||||
dt = order_datetime + timedelta(days=line.customer_lead or 0.0)
|
dt = order_datetime + timedelta(days=line.customer_lead or 0.0)
|
||||||
dates_list.append(dt)
|
dates_list.append(dt)
|
||||||
if dates_list:
|
if dates_list:
|
||||||
|
@ -17,7 +17,6 @@ class PaymentPortal(http.Controller):
|
|||||||
:return html: form containing all values related to the acquirer to
|
:return html: form containing all values related to the acquirer to
|
||||||
redirect customers to the acquirer website """
|
redirect customers to the acquirer website """
|
||||||
success_url = kwargs.get('success_url', '/my')
|
success_url = kwargs.get('success_url', '/my')
|
||||||
callback_method = kwargs.get('callback_method', '')
|
|
||||||
|
|
||||||
order_sudo = request.env['sale.order'].sudo().browse(order_id)
|
order_sudo = request.env['sale.order'].sudo().browse(order_id)
|
||||||
if not order_sudo:
|
if not order_sudo:
|
||||||
@ -28,17 +27,16 @@ class PaymentPortal(http.Controller):
|
|||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if request.env.user == request.env.ref('base.public_user'):
|
||||||
|
save_token = False
|
||||||
|
|
||||||
token = request.env['payment.token'].sudo() # currently no support of payment tokens
|
token = request.env['payment.token'].sudo() # currently no support of payment tokens
|
||||||
tx = request.env['payment.transaction'].sudo()._check_or_create_sale_tx(
|
tx = request.env['payment.transaction'].sudo()._check_or_create_sale_tx(
|
||||||
order_sudo,
|
order_sudo,
|
||||||
acquirer,
|
acquirer,
|
||||||
payment_token=token,
|
payment_token=token,
|
||||||
tx_type='form_save' if save_token else 'form',
|
tx_type='form_save' if save_token else 'form',
|
||||||
add_tx_values={
|
)
|
||||||
'callback_model_id': request.env['ir.model'].sudo().search([('model', '=', order_sudo._name)], limit=1).id,
|
|
||||||
'callback_res_id': order_sudo.id,
|
|
||||||
'callback_method': callback_method,
|
|
||||||
})
|
|
||||||
|
|
||||||
# set the transaction id into the session
|
# set the transaction id into the session
|
||||||
request.session['portal_sale_%s_transaction_id' % order_sudo.id] = tx.id
|
request.session['portal_sale_%s_transaction_id' % order_sudo.id] = tx.id
|
||||||
@ -58,7 +56,6 @@ class PaymentPortal(http.Controller):
|
|||||||
""" Use a token to perform a s2s transaction """
|
""" Use a token to perform a s2s transaction """
|
||||||
error_url = kwargs.get('error_url', '/my')
|
error_url = kwargs.get('error_url', '/my')
|
||||||
success_url = kwargs.get('success_url', '/my')
|
success_url = kwargs.get('success_url', '/my')
|
||||||
callback_method = kwargs.get('callback_method', '')
|
|
||||||
access_token = kwargs.get('access_token')
|
access_token = kwargs.get('access_token')
|
||||||
params = {}
|
params = {}
|
||||||
if access_token:
|
if access_token:
|
||||||
@ -73,7 +70,9 @@ class PaymentPortal(http.Controller):
|
|||||||
token = request.env['payment.token'].sudo().browse(int(pm_id))
|
token = request.env['payment.token'].sudo().browse(int(pm_id))
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
token = False
|
token = False
|
||||||
if not token:
|
|
||||||
|
token_owner = order_sudo.partner_id if request.env.user == request.env.ref('base.public_user') else request.env.user.partner_id
|
||||||
|
if not token or token.partner_id != token_owner:
|
||||||
params['error'] = 'pay_sale_invalid_token'
|
params['error'] = 'pay_sale_invalid_token'
|
||||||
return request.redirect(_build_url_w_params(error_url, params))
|
return request.redirect(_build_url_w_params(error_url, params))
|
||||||
|
|
||||||
@ -83,11 +82,7 @@ class PaymentPortal(http.Controller):
|
|||||||
token.acquirer_id,
|
token.acquirer_id,
|
||||||
payment_token=token,
|
payment_token=token,
|
||||||
tx_type='server2server',
|
tx_type='server2server',
|
||||||
add_tx_values={
|
)
|
||||||
'callback_model_id': request.env['ir.model'].sudo().search([('model', '=', order_sudo._name)], limit=1).id,
|
|
||||||
'callback_res_id': order_sudo.id,
|
|
||||||
'callback_method': callback_method,
|
|
||||||
})
|
|
||||||
|
|
||||||
# set the transaction id into the session
|
# set the transaction id into the session
|
||||||
request.session['portal_sale_%s_transaction_id' % order_sudo.id] = tx.id
|
request.session['portal_sale_%s_transaction_id' % order_sudo.id] = tx.id
|
||||||
|
@ -10,5 +10,16 @@ class CustomerPortal(CustomerPortal):
|
|||||||
def _order_get_page_view_values(self, order, access_token, **kwargs):
|
def _order_get_page_view_values(self, order, access_token, **kwargs):
|
||||||
values = super(CustomerPortal, self)._order_get_page_view_values(order, access_token, **kwargs)
|
values = super(CustomerPortal, self)._order_get_page_view_values(order, access_token, **kwargs)
|
||||||
if values['portal_confirmation'] == 'pay':
|
if values['portal_confirmation'] == 'pay':
|
||||||
values.update(request.env['payment.acquirer']._get_available_payment_input(order.partner_id, order.company_id))
|
payment_inputs = request.env['payment.acquirer']._get_available_payment_input(order.partner_id, order.company_id)
|
||||||
|
# if not connected (using public user), the method _get_available_payment_input will return public user tokens
|
||||||
|
is_public_user = request.env.ref('base.public_user') == request.env.user
|
||||||
|
if is_public_user:
|
||||||
|
# we should not display payment tokens owned by the public user
|
||||||
|
payment_inputs.pop('pms', None)
|
||||||
|
token_count = request.env['payment.token'].sudo().search_count([('acquirer_id.company_id', '=', order.company_id.id),
|
||||||
|
('partner_id', '=', order.partner_id.id),
|
||||||
|
])
|
||||||
|
values['existing_token'] = token_count > 0
|
||||||
|
values.update(payment_inputs)
|
||||||
|
values['partner_id'] = order.partner_id if is_public_user else request.env.user.partner_id
|
||||||
return values
|
return values
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from flectra import api, fields, models, _
|
from flectra import api, fields, models, _
|
||||||
|
from flectra.exceptions import UserError
|
||||||
from flectra.tools import float_compare
|
from flectra.tools import float_compare
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
@ -68,7 +69,6 @@ class PaymentTransaction(models.Model):
|
|||||||
_logger.info('<%s> transaction completed, auto-confirming order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id)
|
_logger.info('<%s> transaction completed, auto-confirming order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id)
|
||||||
if self.sale_order_id.state in ('draft', 'sent'):
|
if self.sale_order_id.state in ('draft', 'sent'):
|
||||||
self.sale_order_id.with_context(send_email=True).action_confirm()
|
self.sale_order_id.with_context(send_email=True).action_confirm()
|
||||||
self._generate_and_pay_invoice()
|
|
||||||
elif self.state not in ['cancel', 'error'] and self.sale_order_id.state == 'draft':
|
elif self.state not in ['cancel', 'error'] and self.sale_order_id.state == 'draft':
|
||||||
_logger.info('<%s> transaction pending/to confirm manually, sending quote email for order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id)
|
_logger.info('<%s> transaction pending/to confirm manually, sending quote email for order %s (ID %s)', self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id)
|
||||||
self.sale_order_id.force_quotation_send()
|
self.sale_order_id.force_quotation_send()
|
||||||
@ -85,8 +85,16 @@ class PaymentTransaction(models.Model):
|
|||||||
# force_company needed for company_dependent fields
|
# force_company needed for company_dependent fields
|
||||||
ctx_company = {'company_id': self.sale_order_id.company_id.id,
|
ctx_company = {'company_id': self.sale_order_id.company_id.id,
|
||||||
'force_company': self.sale_order_id.company_id.id}
|
'force_company': self.sale_order_id.company_id.id}
|
||||||
created_invoice = self.sale_order_id.with_context(**ctx_company).action_invoice_create()
|
|
||||||
created_invoice = self.env['account.invoice'].browse(created_invoice).with_context(**ctx_company)
|
# We might fail to create the invoice because there is no invoiceable lines. This will
|
||||||
|
# raise a UserError and break the workflow. Better catch the error.
|
||||||
|
try:
|
||||||
|
created_invoice = self.sale_order_id.with_context(**ctx_company).action_invoice_create()
|
||||||
|
created_invoice = self.env['account.invoice'].browse(created_invoice).with_context(**ctx_company)
|
||||||
|
except UserError:
|
||||||
|
_logger.warning('<%s> transaction completed, could not auto-generate invoice for %s (ID %s)',
|
||||||
|
self.acquirer_id.provider, self.sale_order_id.name, self.sale_order_id.id, exc_info=True)
|
||||||
|
return
|
||||||
|
|
||||||
if created_invoice:
|
if created_invoice:
|
||||||
_logger.info('<%s> transaction completed, auto-generated invoice %s (ID %s) for %s (ID %s)',
|
_logger.info('<%s> transaction completed, auto-generated invoice %s (ID %s) for %s (ID %s)',
|
||||||
@ -174,7 +182,7 @@ class PaymentTransaction(models.Model):
|
|||||||
'currency_id': order.pricelist_id.currency_id.id,
|
'currency_id': order.pricelist_id.currency_id.id,
|
||||||
'partner_id': order.partner_id.id,
|
'partner_id': order.partner_id.id,
|
||||||
'partner_country_id': order.partner_id.country_id.id,
|
'partner_country_id': order.partner_id.country_id.id,
|
||||||
'reference': self.get_next_reference(order.name),
|
'reference': self._get_next_reference(order.name, acquirer=acquirer),
|
||||||
'sale_order_id': order.id,
|
'sale_order_id': order.id,
|
||||||
}
|
}
|
||||||
if add_tx_values:
|
if add_tx_values:
|
||||||
|
@ -67,6 +67,11 @@
|
|||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="panel-body" t-if="existing_token">
|
||||||
|
<div class="col-md-offset-3 col-md-6">
|
||||||
|
<i class="fa fa-info"></i> You have credits card registered, you can log-in to be able to use them.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//t[@t-if='invoices']" position="before">
|
<xpath expr="//t[@t-if='invoices']" position="before">
|
||||||
|
@ -1,5 +1,17 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from flectra import api, SUPERUSER_ID
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
from . import controllers
|
from . import controllers
|
||||||
|
|
||||||
|
|
||||||
|
def uninstall_hook(cr, registry):
|
||||||
|
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||||
|
env['product.template'].search([
|
||||||
|
('service_type', '=', 'timesheet')
|
||||||
|
]).write({'service_type': 'manual'})
|
||||||
|
env['product.product'].search([
|
||||||
|
('service_type', '=', 'timesheet')
|
||||||
|
]).write({'service_type': 'manual'})
|
||||||
|
@ -31,4 +31,5 @@ have real delivered quantities in sales orders.
|
|||||||
'data/sale_service_demo.xml',
|
'data/sale_service_demo.xml',
|
||||||
],
|
],
|
||||||
'auto_install': True,
|
'auto_install': True,
|
||||||
|
'uninstall_hook': 'uninstall_hook',
|
||||||
}
|
}
|
||||||
|
@ -56,8 +56,8 @@ class SaleTimesheetController(http.Controller):
|
|||||||
dashboard_values['hours'][billable_type] = float_round(data.get('unit_amount'), precision_rounding=hour_rounding)
|
dashboard_values['hours'][billable_type] = float_round(data.get('unit_amount'), precision_rounding=hour_rounding)
|
||||||
dashboard_values['hours']['total'] += float_round(data.get('unit_amount'), precision_rounding=hour_rounding)
|
dashboard_values['hours']['total'] += float_round(data.get('unit_amount'), precision_rounding=hour_rounding)
|
||||||
# rates
|
# rates
|
||||||
dashboard_values['rates'][billable_type] = round(data.get('unit_amount') / dashboard_total_hours * 100, 2)
|
dashboard_values['rates'][billable_type] = dashboard_total_hours and round(data.get('unit_amount') / dashboard_total_hours * 100, 2) or 0
|
||||||
dashboard_values['rates']['total'] += round(data.get('unit_amount') / dashboard_total_hours * 100, 2)
|
dashboard_values['rates']['total'] += dashboard_total_hours and round(data.get('unit_amount') / dashboard_total_hours * 100, 2) or 0
|
||||||
|
|
||||||
# money_amount
|
# money_amount
|
||||||
so_lines = values['timesheet_lines'].mapped('so_line')
|
so_lines = values['timesheet_lines'].mapped('so_line')
|
||||||
|
@ -99,7 +99,8 @@ class AccountInvoice(models.Model):
|
|||||||
line_revenue = timesheet_line.timesheet_revenue * price_subtotal_inv / price_subtotal_sol
|
line_revenue = timesheet_line.timesheet_revenue * price_subtotal_inv / price_subtotal_sol
|
||||||
total_revenue_per_currency[timesheet_line.company_currency_id.id] += line_revenue
|
total_revenue_per_currency[timesheet_line.company_currency_id.id] += line_revenue
|
||||||
else:
|
else:
|
||||||
total_revenue_per_currency[timesheet_line.company_currency_id.id] += timesheet_line.timesheet_revenue
|
line_revenue = timesheet_line.timesheet_revenue
|
||||||
|
total_revenue_per_currency[timesheet_line.company_currency_id.id] += line_revenue
|
||||||
else: # last line: add the difference to avoid rounding problem
|
else: # last line: add the difference to avoid rounding problem
|
||||||
last_price_subtotal_inv = invoice_line.currency_id.compute(invoice_line.price_subtotal, timesheet_line.company_currency_id)
|
last_price_subtotal_inv = invoice_line.currency_id.compute(invoice_line.price_subtotal, timesheet_line.company_currency_id)
|
||||||
total_revenue = sum([self.env['res.currency'].browse(currency_id).compute(amount, timesheet_line.company_currency_id) for currency_id, amount in total_revenue_per_currency.items()])
|
total_revenue = sum([self.env['res.currency'].browse(currency_id).compute(amount, timesheet_line.company_currency_id) for currency_id, amount in total_revenue_per_currency.items()])
|
||||||
|
@ -70,8 +70,9 @@ class ProjectTask(models.Model):
|
|||||||
@api.model
|
@api.model
|
||||||
def create(self, values):
|
def create(self, values):
|
||||||
# sub task has the same so line than their parent
|
# sub task has the same so line than their parent
|
||||||
if 'parent_id' in values:
|
parent_id = values['parent_id'] if 'parent_id' in values else self.env.context.get('default_parent_id')
|
||||||
values['sale_line_id'] = self.env['project.task'].browse(values['parent_id']).sudo().sale_line_id.id
|
if parent_id:
|
||||||
|
values['sale_line_id'] = self.env['project.task'].browse(parent_id).sudo().sale_line_id.id
|
||||||
return super(ProjectTask, self).create(values)
|
return super(ProjectTask, self).create(values)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
@ -175,6 +175,7 @@ class SaleOrderLine(models.Model):
|
|||||||
@api.multi
|
@api.multi
|
||||||
def _analytic_compute_delivered_quantity_domain(self):
|
def _analytic_compute_delivered_quantity_domain(self):
|
||||||
domain = super(SaleOrderLine, self)._analytic_compute_delivered_quantity_domain()
|
domain = super(SaleOrderLine, self)._analytic_compute_delivered_quantity_domain()
|
||||||
|
domain = expression.AND([domain, [('project_id', '=', False)]])
|
||||||
timesheet_domain = self._timesheet_compute_delivered_quantity_domain()
|
timesheet_domain = self._timesheet_compute_delivered_quantity_domain()
|
||||||
return expression.OR([domain, timesheet_domain])
|
return expression.OR([domain, timesheet_domain])
|
||||||
|
|
||||||
|
@ -17,6 +17,11 @@ class TestSaleTimesheet(TestSale):
|
|||||||
# after the 6 june of current year.
|
# after the 6 june of current year.
|
||||||
self.env.ref('base.rateUSDbis').unlink()
|
self.env.ref('base.rateUSDbis').unlink()
|
||||||
|
|
||||||
|
# Make sure the company is in USD
|
||||||
|
self.env.cr.execute(
|
||||||
|
"UPDATE res_company SET currency_id = %s WHERE id = %s",
|
||||||
|
[self.env.ref('base.USD').id, self.env.user.company_id.id])
|
||||||
|
|
||||||
# create project
|
# create project
|
||||||
self.project = self.env['project.project'].create({
|
self.project = self.env['project.project'].create({
|
||||||
'name': 'Project for my timesheets',
|
'name': 'Project for my timesheets',
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
string="Sales Order"/>
|
string="Sales Order"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
<field name="user_id" position="after">
|
<field name="user_id" position="after">
|
||||||
<field name="sale_line_id" string="Sales Order Item" attrs="{'invisible': [('partner_id', '=', False)], 'readonly': [('parent_id', '!=', False)]}" options='{"no_open": True, "no_create": True}' context="{'sale_show_order_product_name': True}"/>
|
<field name="sale_line_id" string="Sales Order Item" attrs="{'invisible': [('partner_id', '=', False)], 'readonly': [('parent_id', '!=', False)]}" options='{"no_open": True, "no_create": True}'/>
|
||||||
</field>
|
</field>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
Loading…
Reference in New Issue
Block a user