Merge branch 'master-bhumika-06072018' into 'master-patch-july-2018'

Master bhumika 06072018

See merge request flectra-hq/flectra!90
This commit is contained in:
Parthiv Patel 2018-07-13 09:21:38 +00:00
commit 24fd7e5c2f
31 changed files with 355 additions and 80 deletions

View File

@ -33,7 +33,7 @@ class DeliveryCarrier(models.Model):
# Internals for shipping providers #
# -------------------------------- #
name = fields.Char(required=True)
name = fields.Char(required=True, translate=True)
active = fields.Boolean(default=True)
sequence = fields.Integer(help="Determine the display order", default=10)
# This field will be overwritten by internal shipping providers by adding their own type (ex: 'fedex')

View File

@ -91,7 +91,7 @@ class SaleOrder(models.Model):
# Create the sales order line
values = {
'order_id': self.id,
'name': carrier.name,
'name': carrier.with_context(lang=self.partner_id.lang).name,
'product_uom_qty': 1,
'product_uom': carrier.product_id.uom_id.id,
'product_id': carrier.product_id.id,
@ -117,3 +117,7 @@ class SaleOrderLine(models.Model):
if not line.product_id or not line.product_uom or not line.product_uom_qty:
return 0.0
line.product_qty = line.product_uom._compute_quantity(line.product_uom_qty, line.product_id.uom_id)
def _is_delivery(self):
self.ensure_one()
return self.is_delivery

View File

@ -161,8 +161,11 @@ class StockPicking(models.Model):
def send_to_shipper(self):
self.ensure_one()
res = self.carrier_id.send_shipping(self)[0]
if self.carrier_id.free_over and self.sale_id and self.sale_id._compute_amount_total_without_delivery() >= self.carrier_id.amount:
res['exact_price'] = 0.0
self.carrier_price = res['exact_price']
self.carrier_tracking_ref = res['tracking_number']
if res['tracking_number']:
self.carrier_tracking_ref = res['tracking_number']
order_currency = self.sale_id.currency_id or self.company_id.currency_id
msg = _("Shipment sent to carrier %s for shipping with tracking number %s<br/>Cost: %.2f %s") % (self.carrier_id.name, self.carrier_tracking_ref, self.carrier_price, order_currency.name)
self.message_post(body=msg)

View File

@ -31,7 +31,10 @@ class TestDeliveryCost(common.TransactionCase):
self.free_delivery = self.env.ref('delivery.free_delivery_carrier')
# as the tests hereunder assume all the prices in USD, we must ensure
# that the company actually uses USD
self.env.user.company_id.write({'currency_id': self.env.ref('base.USD').id})
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])
self.pricelist.currency_id = self.env.ref('base.USD').id
def test_00_delivery_cost(self):
# In order to test Carrier Cost

View File

@ -438,9 +438,14 @@ class EventRegistration(models.Model):
@api.multi
def message_get_suggested_recipients(self):
recipients = super(EventRegistration, self).message_get_suggested_recipients()
public_users = self.env['res.users'].sudo()
public_groups = self.env.ref("base.group_public", raise_if_not_found=False)
if public_groups:
public_users = public_groups.sudo().with_context(active_test=False).mapped("users")
try:
for attendee in self:
if attendee.partner_id:
is_public = attendee.sudo().with_context(active_test=False).partner_id.user_ids in public_users if public_users else False
if attendee.partner_id and not is_public:
attendee._message_add_suggested_recipient(recipients, partner=attendee.partner_id, reason=_('Customer'))
elif attendee.email:
attendee._message_add_suggested_recipient(recipients, email=attendee.email, reason=_('Customer Email'))

View File

@ -4,7 +4,12 @@ from datetime import datetime
from dateutil.relativedelta import relativedelta
from flectra import api, fields, models, tools
from flectra.tools import exception_to_unicode
from flectra.tools.translate import _
import random
import logging
_logger = logging.getLogger(__name__)
_INTERVALS = {
'hours': lambda interval: relativedelta(hours=interval),
@ -114,13 +119,52 @@ class EventMailScheduler(models.Model):
self.write({'mail_sent': True})
return True
@api.model
def _warn_template_error(self, scheduler, exception):
# We warn ~ once by hour ~ instead of every 10 min if the interval unit is more than 'hours'.
if random.random() < 0.1666 or scheduler.interval_unit in ('now', 'hours'):
ex_s = exception_to_unicode(exception)
try:
event, template = scheduler.event_id, scheduler.template_id
emails = list(set([event.organizer_id.email, event.user_id.email, template.write_uid.email]))
subject = _("WARNING: Event Scheduler Error for event: %s" % event.name)
body = _("""Event Scheduler for:
- Event: %s (%s)
- Scheduled: %s
- Template: %s (%s)
Failed with error:
- %s
You receive this email because you are:
- the organizer of the event,
- or the responsible of the event,
- or the last writer of the template."""
% (event.name, event.id, scheduler.scheduled_date, template.name, template.id, ex_s))
email = self.env['ir.mail_server'].build_email(
email_from=self.env.user.email,
email_to=emails,
subject=subject, body=body,
)
self.env['ir.mail_server'].send_email(email)
except Exception as e:
_logger.error("Exception while sending traceback by email: %s.\n Original Traceback:\n%s", e, exception)
pass
@api.model
def run(self, autocommit=False):
schedulers = self.search([('done', '=', False), ('scheduled_date', '<=', datetime.strftime(fields.datetime.now(), tools.DEFAULT_SERVER_DATETIME_FORMAT))])
for scheduler in schedulers:
scheduler.execute()
if autocommit:
self.env.cr.commit()
try:
with self.env.cr.savepoint():
scheduler.execute()
except Exception as e:
_logger.exception(e)
self.invalidate_cache()
self._warn_template_error(scheduler, e)
else:
if autocommit:
self.env.cr.commit()
return True

View File

@ -9,6 +9,8 @@ class ResPartner(models.Model):
event_count = fields.Integer("Events", compute='_compute_event_count', help="Number of events the partner has participated.")
def _compute_event_count(self):
if not self.user_has_groups('event.group_event_user'):
return
for partner in self:
partner.event_count = self.env['event.event'].search_count([('registration_ids.partner_id', 'child_of', partner.ids)])

View File

@ -18,7 +18,7 @@
<img t-att-src="'data:image/png;base64,%s' % to_text(o.event_id.organizer_id.company_id.logo_web)" style="max-height:1cm; max-width:4cm;"/>
</span>
<h5 t-field="o.event_id.name"/>
<h5>( <i class="fa fa-clock-o"></i> <span itemprop="startDate" t-field="o.event_id.date_begin" t-options='{"hide_seconds": True}'> </span> <i>to</i> <span itemprop="endDate" t-field="o.event_id.date_end" t-options='{"hide_seconds": True}'> </span> )</h5>
<h5>( <i class="fa fa-clock-o"></i> <span itemprop="startDate" t-field="o.event_id.with_context(tz=o.event_id.date_tz).date_begin" t-options='{"hide_seconds": True}'> </span> <i>to</i> <span itemprop="endDate" t-field="o.event_id.with_context(tz=o.event_id.date_tz).date_end" t-options='{"hide_seconds": True}'> </span> )</h5>
</div>
<div class="row">
<div class="col-xs-12 text-center" id="o_event_name">

View File

@ -41,5 +41,12 @@
]
</field>
</record>
<record model="ir.rule" id="event_registration_portal">
<field name="name">Event/Registration: Portal</field>
<field name="model_id" ref="model_event_registration"/>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
<field name="domain_force">['|', ('email', '=', user.partner_id.email), ('partner_id', '=', user.partner_id.id)]
</field>
</record>
</data>
</flectra>

View File

@ -5,7 +5,8 @@ access_event_event_portal,event.event.portal,model_event_event,,1,0,0,0
access_event_event_user,event.event.user,model_event_event,event.group_event_user,1,0,0,0
access_event_event_manager,event.event.manager,model_event_event,event.group_event_manager,1,1,1,1
access_event_registration,event.registration,model_event_registration,event.group_event_user,1,1,1,1
access_event_registration_portal,event.registration,model_event_registration,,1,0,0,0
access_event_registration_employee,event.registration,model_event_registration,base.group_user,1,0,0,0
access_event_registration_portal,event.registration,model_event_registration,base.group_portal,1,0,0,0
access_event_mail,event.mail,model_event_mail,event.group_event_user,1,0,0,0
access_event_mail_manager,event.mail manager,model_event_mail,event.group_event_manager,1,1,1,1
access_event_mail_registration,event.mail.registration,model_event_mail_registration,event.group_event_user,1,0,0,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
5 access_event_event_user event.event.user model_event_event event.group_event_user 1 0 0 0
6 access_event_event_manager event.event.manager model_event_event event.group_event_manager 1 1 1 1
7 access_event_registration event.registration model_event_registration event.group_event_user 1 1 1 1
8 access_event_registration_portal access_event_registration_employee event.registration model_event_registration base.group_user 1 0 0 0
9 access_event_registration_portal event.registration model_event_registration base.group_portal 1 0 0 0
10 access_event_mail event.mail model_event_mail event.group_event_user 1 0 0 0
11 access_event_mail_manager event.mail manager model_event_mail event.group_event_manager 1 1 1 1
12 access_event_mail_registration event.mail.registration model_event_mail_registration event.group_event_user 1 0 0 0

View File

@ -5,11 +5,13 @@
<field name="name">view.res.partner.form.event.inherited</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="groups_id" eval="[(4, ref('event.group_event_user'))]"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button class="oe_stat_button"
type="object"
icon="fa-ticket"
groups="event.group_event_user"
name="action_event_view" attrs="{'invisible': [('event_count','=', 0)]}">
<field string="Events" name="event_count" widget="statinfo"/>
</button>

View File

@ -27,6 +27,7 @@ this event.
'data/event_sale_data.xml',
'report/event_event_templates.xml',
'security/ir.model.access.csv',
'security/event_security.xml',
'wizard/event_edit_registration.xml',
],
'demo': ['data/event_demo.xml'],

View File

@ -41,7 +41,7 @@ class SaleOrderLine(models.Model):
order line has a product_uom_qty attribute that will be the number of
registrations linked to this line. This method update existing registrations
and create new one for missing one. """
Registration = self.env['event.registration']
Registration = self.env['event.registration'].sudo()
registrations = Registration.search([('sale_order_line_id', 'in', self.ids), ('state', '!=', 'cancel')])
for so_line in self.filtered('event_id'):
existing_registrations = registrations.filtered(lambda self: self.sale_order_line_id.id == so_line.id)

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<flectra>
<data noupdate="0">
<record id="sales_team.group_sale_salesman" model="res.groups">
<field name="implied_ids" eval="[(4, ref('event.group_event_user'))]"/>
</record>
</data>
</flectra>

View File

@ -19,9 +19,10 @@ class FleetVehicle(models.Model):
name = fields.Char(compute="_compute_vehicle_name", store=True)
active = fields.Boolean('Active', default=True, track_visibility="onchange")
company_id = fields.Many2one('res.company', 'Company')
license_plate = fields.Char(required=True, help='License plate number of the vehicle (i = plate number for a car)')
license_plate = fields.Char(required=True, track_visibility="onchange",
help='License plate number of the vehicle (i = plate number for a car)')
vin_sn = fields.Char('Chassis Number', help='Unique number written on the vehicle motor (VIN/SN number)', copy=False)
driver_id = fields.Many2one('res.partner', 'Driver', help='Driver of the vehicle', copy=False)
driver_id = fields.Many2one('res.partner', 'Driver', track_visibility="onchange", help='Driver of the vehicle', copy=False)
model_id = fields.Many2one('fleet.vehicle.model', 'Model', required=True, help='Model of the vehicle')
log_fuel = fields.One2many('fleet.vehicle.log.fuel', 'vehicle_id', 'Fuel Logs')
log_services = fields.One2many('fleet.vehicle.log.services', 'vehicle_id', 'Services Logs')
@ -74,10 +75,10 @@ class FleetVehicle(models.Model):
('driver_id_unique', 'UNIQUE(driver_id)', 'Only one car can be assigned to the same employee!')
]
@api.depends('model_id', 'license_plate')
@api.depends('model_id.brand_id.name', 'model_id.name', 'license_plate')
def _compute_vehicle_name(self):
for record in self:
record.name = record.model_id.brand_id.name + '/' + record.model_id.name + '/' + record.license_plate
record.name = record.model_id.brand_id.name + '/' + record.model_id.name + '/' + (record.license_plate or _('No Plate'))
def _get_odometer(self):
FleetVehicalOdometer = self.env['fleet.vehicle.odometer']

View File

@ -259,7 +259,7 @@ class Goal(models.Model):
If the end date is passed (at least +1 day, time not considered) without
the target value being reached, the goal is set as failed."""
goals_by_definition = {}
for goal in self:
for goal in self.with_context(prefetch_fields=False):
goals_by_definition.setdefault(goal.definition_id, []).append(goal)
for definition, goals in goals_by_definition.items():

View File

@ -677,7 +677,7 @@ class GoogleCalendar(models.AbstractModel):
try:
all_event_from_google = self.get_event_synchro_dict(lastSync=lastSync)
except requests.HTTPError as e:
if e.response.code == 410: # GONE, Google is lost.
if e.response.status_code == 410: # GONE, Google is lost.
# we need to force the rollback from this cursor, because it locks my res_users but I need to write in this tuple before to raise.
self.env.cr.rollback()
self.env.user.write({'google_calendar_last_sync_date': False})
@ -842,7 +842,7 @@ class GoogleCalendar(models.AbstractModel):
try:
# if already deleted from gmail or never created
recs.delete_an_event(current_event[0])
except Exception as e:
except requests.exceptions.HTTPError as e:
if e.response.status_code in (401, 410,):
pass
else:

View File

@ -24,6 +24,7 @@
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.config.settings</field>
<field name="view_mode">form</field>
<field name="context">{'module' : 'general_settings'}</field>
<field name="target">inline</field>
</record>

View File

@ -2,10 +2,12 @@
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from collections import OrderedDict
from operator import itemgetter
from flectra import http, _
from flectra.http import request
from flectra.addons.portal.controllers.portal import get_records_pager, CustomerPortal, pager as portal_pager
from flectra.tools import groupby as groupbyelem
from flectra.osv.expression import OR
@ -78,6 +80,7 @@ class CustomerPortal(CustomerPortal):
@http.route(['/my/tasks', '/my/tasks/page/<int:page>'], type='http', auth="user", website=True)
def portal_my_tasks(self, page=1, date_begin=None, date_end=None, sortby=None, filterby=None, search=None, search_in='content', **kw):
groupby = kw.get('groupby', 'project') #TODO master fix this
values = self._prepare_portal_layout_values()
searchbar_sortings = {
'date': {'label': _('Newest'), 'order': 'create_date desc'},
@ -95,6 +98,10 @@ class CustomerPortal(CustomerPortal):
'stage': {'input': 'stage', 'label': _('Search in Stages')},
'all': {'input': 'all', 'label': _('Search in All')},
}
searchbar_groupby = {
'none': {'input': 'none', 'label': _('None')},
'project': {'input': 'project', 'label': _('Project')},
}
# extends filterby criteria with project (criteria name is the project id)
# Note: portal users can't view projects they don't follow
projects = request.env['project.project'].sudo().search([('privacy_visibility', '=', 'portal')])
@ -136,28 +143,37 @@ class CustomerPortal(CustomerPortal):
# pager
pager = portal_pager(
url="/my/tasks",
url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby, 'filterby': filterby},
url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby, 'filterby': filterby, 'search_in': search_in, 'search': search},
total=task_count,
page=page,
step=self._items_per_page
)
# content according to pager and archive selected
if groupby == 'project':
order = "project_id, %s" % order # force sort on project first to group by project in view
tasks = request.env['project.task'].search(domain, order=order, limit=self._items_per_page, offset=pager['offset'])
request.session['my_tasks_history'] = tasks.ids[:100]
if groupby == 'project':
grouped_tasks = [request.env['project.task'].concat(*g) for k, g in groupbyelem(tasks, itemgetter('project_id'))]
else:
grouped_tasks = [tasks]
values.update({
'date': date_begin,
'date_end': date_end,
'projects': projects,
'tasks': tasks,
'grouped_tasks': grouped_tasks,
'page_name': 'task',
'archive_groups': archive_groups,
'default_url': '/my/tasks',
'pager': pager,
'searchbar_sortings': searchbar_sortings,
'searchbar_groupby': searchbar_groupby,
'searchbar_inputs': searchbar_inputs,
'search_in': search_in,
'sortby': sortby,
'groupby': groupby,
'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
'filterby': filterby,
})

View File

@ -4,7 +4,7 @@
from lxml import etree
from flectra import api, fields, models, tools, SUPERUSER_ID, _
from flectra.exceptions import UserError, AccessError
from flectra.exceptions import UserError, AccessError, ValidationError
from flectra.tools.safe_eval import safe_eval
from datetime import timedelta, date
@ -469,7 +469,7 @@ class Task(models.Model):
legend_done = fields.Char(related='stage_id.legend_done', string='Kanban Valid Explanation', readonly=True, related_sudo=False)
legend_normal = fields.Char(related='stage_id.legend_normal', string='Kanban Ongoing Explanation', readonly=True, related_sudo=False)
parent_id = fields.Many2one('project.task', string='Parent Task', index=True)
child_ids = fields.One2many('project.task', 'parent_id', string="Sub-tasks")
child_ids = fields.One2many('project.task', 'parent_id', string="Sub-tasks", context={'active_test': False})
subtask_project_id = fields.Many2one('project.project', related="project_id.subtask_project_id", string='Sub-task Project', readonly=True)
subtask_count = fields.Integer(compute='_compute_subtask_count', type='integer', string="Sub-task count")
email_from = fields.Char(string='Email', help="These people will receive email.", index=True)
@ -598,6 +598,12 @@ class Task(models.Model):
for task in self:
task.subtask_count = self.search_count([('id', 'child_of', task.id), ('id', '!=', task.id)])
@api.constrains('parent_id')
def _check_parent_id(self):
for task in self:
if not task._check_recursion():
raise ValidationError(_('Error! You cannot create recursive hierarchy of task(s).'))
@api.constrains('parent_id')
def _check_subtask_project(self):
for task in self:
@ -796,7 +802,7 @@ class Task(models.Model):
groups = [new_group] + groups
for group_name, group_method, group_data in groups:
if group_name in ['customer', 'portal']:
if group_name == 'customer':
continue
group_data['has_button_access'] = True
@ -874,12 +880,12 @@ class Task(models.Model):
@api.multi
def message_get_suggested_recipients(self):
recipients = super(Task, self).message_get_suggested_recipients()
for task in self.filtered('partner_id'):
reason = _('Customer Email') if task.partner_id.email else _('Customer')
for task in self:
if task.partner_id:
reason = _('Customer Email') if task.partner_id.email else _('Customer')
task._message_add_suggested_recipient(recipients, partner=task.partner_id, reason=reason)
elif task.email_from:
task._message_add_suggested_recipient(recipients, partner=task.email_from, reason=reason)
task._message_add_suggested_recipient(recipients, email=task.email_from, reason=_('Customer Email'))
return recipients
@api.multi
@ -926,6 +932,20 @@ class Task(models.Model):
'type': 'ir.actions.act_window'
}
def action_subtask(self):
action = self.env.ref('project.project_task_action_sub_task').read()[0]
ctx = self.env.context.copy()
ctx.update({
'default_parent_id' : self.id,
'default_project_id' : self.env.context.get('project_id', self.subtask_project_id.id),
'default_name' : self.env.context.get('name', self.name) + ':',
'default_partner_id' : self.env.context.get('partner_id', self.partner_id.id),
'search_default_project_id': self.env.context.get('project_id', self.subtask_project_id.id),
})
action['context'] = ctx
action['domain'] = [('id', 'child_of', self.id), ('id', '!=', self.id)]
return action
class AccountAnalyticAccount(models.Model):
_inherit = 'account.analytic.account'

View File

@ -130,36 +130,41 @@
<h3>Tasks
<t t-call="portal.portal_searchbar"/>
</h3>
<t t-if="not tasks">
<t t-if="not grouped_tasks">
<div class="alert alert-warning mt8" role="alert">
There are no tasks.
</div>
</t>
<div t-if="tasks" class="panel panel-default">
<div class="table-responsive"><table class="table table-hover o_portal_my_doc_table">
<thead>
<tr class="active">
<th class="col-md-9">Task</th>
<th class="col-md-2">Stage</th>
<th>Ref</th>
</tr>
</thead>
<tbody>
<t t-foreach="tasks" t-as="task">
<tr>
<td>
<a t-attf-href="/my/task/#{task.id}?{{ keep_query() }}"><span t-field="task.name"/></a>
</td>
<td>
<span class="label label-info" title="Current stage of the task" t-esc="task.stage_id.name" />
</td>
<td>
<span t-esc="task.id"/>
</td>
</tr>
</t>
</tbody>
</table></div>
<div t-if="grouped_tasks" class="panel panel-default">
<div class="table-responsive">
<t t-foreach="grouped_tasks" t-as="tasks">
<table class="table table-hover o_portal_my_doc_table">
<thead>
<tr class="active">
<th t-if="groupby == 'none'" class="col-md-9">Name</th>
<th t-else="" class="col-md-9"><span t-field="tasks[0].project_id.label_tasks"/> for project: <span t-field="tasks[0].project_id.name"/></th>
<th class="col-md-2">Stage</th>
<th>Ref</th>
</tr>
</thead>
<tbody>
<t t-foreach="tasks" t-as="task">
<tr>
<td>
<a t-attf-href="/my/task/#{task.id}?{{ keep_query() }}"><span t-field="task.name"/></a>
</td>
<td>
<span class="label label-info" title="Current stage of the task" t-esc="task.stage_id.name" />
</td>
<td>
<span t-esc="task.id"/>
</td>
</tr>
</t>
</tbody>
</table>
</t>
</div>
</div>
<div t-if="pager" class="o_portal_pager text-center">
<t t-call="portal.pager"/>

View File

@ -413,7 +413,7 @@
<sheet string="Task">
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" icon="fa-tasks" type="object" name="action_open_parent_task" string="Parent Task" attrs="{'invisible' : [('parent_id', '=', False)]}" groups="project.group_subtask_project"/>
<button name="%(project_task_action_sub_task)d" type="action" class="oe_stat_button" icon="fa-tasks"
<button name="action_subtask" type="object" class="oe_stat_button" icon="fa-tasks"
attrs="{'invisible' : [('parent_id', '!=', False)]}"
context="{'project_id': subtask_project_id, 'name': name, 'partner_id': partner_id}" groups="project.group_subtask_project">
<field string="Sub-tasks" name="subtask_count" widget="statinfo"/>

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from datetime import datetime
from datetime import datetime, time
from dateutil.relativedelta import relativedelta
from flectra import fields

View File

@ -5,7 +5,7 @@
<!--Email template -->
<record id="email_template_edi_purchase" model="mail.template">
<field name="name">RFQ - Send by Email</field>
<field name="email_from">${(object.create_uid.email and '%s &lt;%s&gt;' % (object.create_uid.name, object.create_uid.email) or '')|safe}</field>
<field name="email_from">${(object.create_uid.email and '&quot;%s&quot; &lt;%s&gt;' % (object.create_uid.name, object.create_uid.email) or '')|safe}</field>
<field name="subject">${object.company_id.name} Order (Ref ${object.name or 'n/a' })</field>
<field name="partner_to">${object.partner_id.id}</field>
<field name="model_id" ref="purchase.model_purchase_order"/>
@ -46,7 +46,7 @@ from ${object.company_id.name}.
<!--Email template -->
<record id="email_template_edi_purchase_done" model="mail.template">
<field name="name">Purchase Order - Send by Email</field>
<field name="email_from">${(object.create_uid.email and '%s &lt;%s&gt;' % (object.create_uid.name, object.create_uid.email) or '')|safe}</field>
<field name="email_from">${(object.create_uid.email and '&quot;%s&quot; &lt;%s&gt;' % (object.create_uid.name, object.create_uid.email) or '')|safe}</field>
<field name="subject">${object.company_id.name} Order (Ref ${object.name or 'n/a' })</field>
<field name="partner_to">${object.partner_id.id}</field>
<field name="model_id" ref="purchase.model_purchase_order"/>
@ -86,6 +86,7 @@ from ${object.company_id.name}.
<field name="auto_delete" eval="True"/>
<field name="body_html"><![CDATA[<html>
<head></head>
% set company = record and record.company_id or ctx.get('company')
<body style="margin: 0; padding: 0;">
<table border="0" width="100%" cellpadding="0" bgcolor="#ededed" style="padding: 20px; background-color: #ededed" summary="o_mail_notification">
<tbody>
@ -101,7 +102,7 @@ from ${object.company_id.name}.
</span>
</td>
<td valign="middle" align="right">
<img src="/logo.png" style="padding: 0px; margin: 0px; height: auto; width: 80px;" alt="${user.company_id.name}">
<img src="/logo.png?company=${company.id}" style="padding: 0px; margin: 0px; height: auto; width: 80px;" alt="${company.name}">
</td>
</tr>
</table>
@ -127,16 +128,16 @@ from ${object.company_id.name}.
<table width="590" border="0" cellpadding="0" bgcolor="#875A7B" style="min-width: 590px; background-color: rgb(135,90,123); padding: 20px;">
<tr>
<td valign="middle" align="left" style="color: #fff; padding-top: 10px; padding-bottom: 10px; font-size: 12px;">
${user.company_id.name}<br/>
${user.company_id.phone or ''}
${company.name}<br/>
${company.phone or ''}
</td>
<td valign="middle" align="right" style="color: #fff; padding-top: 10px; padding-bottom: 10px; font-size: 12px;">
% if user.company_id.email:
<a href="mailto:${user.company_id.email}" style="text-decoration:none; color: white;">${user.company_id.email}</a><br/>
% if company.email:
<a href="mailto:${company.email}" style="text-decoration:none; color: white;">${company.email}</a><br/>
% endif
% if user.company_id.website:
<a href="${user.company_id.website}" style="text-decoration:none; color: white;">
${user.company_id.website}
% if company.website:
<a href="${company.website}" style="text-decoration:none; color: white;">
${company.website}
</a>
% endif
</td>

View File

@ -6,3 +6,6 @@
warehouse = self.browse(ref('stock.' + wh_ref))
#Force the rewriting of route and rule
warehouse.write({'buy_to_resupply': True})
partner_id = ref('stock.res_partner_company_1')
warehouse = self.search([('partner_id', '=', partner_id)])
warehouse.write({'buy_to_resupply': True})

View File

@ -161,7 +161,15 @@ class AccountInvoice(models.Model):
#for average/fifo/lifo costing method, fetch real cost price from incomming moves
valuation_price_unit = i_line.purchase_line_id.product_uom._compute_price(i_line.purchase_line_id.price_unit, i_line.uom_id)
stock_move_obj = self.env['stock.move']
valuation_stock_move = stock_move_obj.search([('purchase_line_id', '=', i_line.purchase_line_id.id), ('state', '=', 'done')])
valuation_stock_move = stock_move_obj.search([
('purchase_line_id', '=', i_line.purchase_line_id.id),
('state', '=', 'done'), ('product_qty', '!=', 0.0)
])
if self.type == 'in_refund':
valuation_stock_move = valuation_stock_move.filtered(lambda m: m._is_out())
elif self.type == 'in_invoice':
valuation_stock_move = valuation_stock_move.filtered(lambda m: m._is_in())
if valuation_stock_move:
valuation_price_unit_total = 0
valuation_total_qty = 0

View File

@ -96,7 +96,7 @@ class PurchaseOrder(models.Model):
@api.depends('picking_ids', 'picking_ids.state')
def _compute_is_shipped(self):
for order in self:
if order.picking_ids and all([x.state == 'done' for x in order.picking_ids]):
if order.picking_ids and all([x.state in ['done', 'cancel'] for x in order.picking_ids]):
order.is_shipped = True
@api.constrains('picking_type_id', 'branch_id')
@ -352,14 +352,15 @@ class PurchaseOrder(models.Model):
@api.multi
def print_quotation(self):
self.write({'state': "sent"})
return self.env.ref('purchase.report_purchase_quotation').report_action(self)
@api.multi
def button_approve(self, force=False):
self.write({'state': 'purchase', 'date_approve': fields.Date.context_today(self)})
self._create_picking()
if self.company_id.po_lock == 'lock':
self.write({'state': 'done'})
self.filtered(
lambda p: p.company_id.po_lock == 'lock').write({'state': 'done'})
return {}
@api.multi
@ -406,6 +407,8 @@ class PurchaseOrder(models.Model):
for pick in order.picking_ids.filtered(lambda r: r.state != 'cancel'):
pick.action_cancel()
order.order_line.write({'move_dest_ids':[(5,0,0)]})
self.write({'state': 'cancel'})
@api.multi
@ -940,12 +943,13 @@ class ProcurementRule(models.Model):
if domain in cache:
po = cache[domain]
else:
po = self.env['purchase.order'].search([dom for dom in domain])
po = self.env['purchase.order'].sudo().search([dom for dom in domain])
po = po[0] if po else False
cache[domain] = po
if not po:
vals = self._prepare_purchase_order(product_id, product_qty, product_uom, origin, values, partner)
po = self.env['purchase.order'].create(vals)
company_id = values.get('company_id') and values['company_id'].id or self.env.user.company_id.id
po = self.env['purchase.order'].with_context(force_company=company_id).sudo().create(vals)
cache[domain] = po
elif not po.origin or origin not in po.origin.split(', '):
if po.origin:
@ -966,7 +970,7 @@ class ProcurementRule(models.Model):
break
if not po_line:
vals = self._prepare_purchase_order_line(product_id, product_qty, product_uom, values, po, supplier)
self.env['purchase.order.line'].create(vals)
self.env['purchase.order.line'].sudo().create(vals)
def _get_purchase_schedule_date(self, values):
"""Return the datetime value to use as Schedule Date (``date_planned``) for the
@ -1049,20 +1053,20 @@ class ProcurementRule(models.Model):
def _prepare_purchase_order(self, product_id, product_qty, product_uom, origin, values, partner):
schedule_date = self._get_purchase_schedule_date(values)
purchase_date = self._get_purchase_order_date(product_id, product_qty, product_uom, values, partner, schedule_date)
fpos = self.env['account.fiscal.position'].with_context(company_id=values['company_id'].id).get_fiscal_position(partner.id)
fpos = self.env['account.fiscal.position'].with_context(force_company=values['company_id'].id).get_fiscal_position(partner.id)
gpo = self.group_propagation_option
group = (gpo == 'fixed' and self.group_id.id) or \
(gpo == 'propagate' and values['group_id'].id) or False
(gpo == 'propagate' and values.get('group_id') and values['group_id'].id) or False
return {
'partner_id': partner.id,
'picking_type_id': self.picking_type_id.id,
'company_id': values['company_id'].id,
'currency_id': partner.property_purchase_currency_id.id or self.env.user.company_id.currency_id.id,
'currency_id': partner.with_context(force_company=values['company_id'].id).property_purchase_currency_id.id or self.env.user.company_id.currency_id.id,
'dest_address_id': values.get('partner_dest_id', False) and values['partner_dest_id'].id,
'origin': origin,
'payment_term_id': partner.property_supplier_payment_term_id.id,
'payment_term_id': partner.with_context(force_company=values['company_id'].id).property_supplier_payment_term_id.id,
'date_order': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'fiscal_position_id': fpos,
'group_id': group
@ -1078,7 +1082,7 @@ class ProcurementRule(models.Model):
domain = super(ProcurementRule, self)._make_po_get_domain(values, partner)
gpo = self.group_propagation_option
group = (gpo == 'fixed' and self.group_id) or \
(gpo == 'propagate' and values['group_id']) or False
(gpo == 'propagate' and 'group_id' in values and values['group_id']) or False
domain += (
('partner_id', '=', partner.id),

View File

@ -27,7 +27,7 @@ class StockMove(models.Model):
_inherit = 'stock.move'
purchase_line_id = fields.Many2one('purchase.order.line',
'Purchase Order Line', ondelete='set null', index=True, readonly=True, copy=False)
'Purchase Order Line', ondelete='set null', index=True, readonly=True)
created_purchase_line_id = fields.Many2one('purchase.order.line',
'Created Purchase Order Line', ondelete='set null', readonly=True, copy=False)

View File

@ -90,7 +90,7 @@ class PurchaseReport(models.Model):
join res_partner partner on s.partner_id = partner.id
left join product_product p on (l.product_id=p.id)
left join product_template t on (p.product_tmpl_id=t.id)
LEFT JOIN ir_property ip ON (ip.name='standard_price' AND ip.res_id=CONCAT('product.template,',t.id) AND ip.company_id=s.company_id)
LEFT JOIN ir_property ip ON (ip.name='standard_price' AND ip.res_id=CONCAT('product.product,',p.id) AND ip.company_id=s.company_id)
left join product_uom u on (u.id=l.product_uom)
left join product_uom u2 on (u2.id=t.uom_id)
left join stock_picking_type spt on (spt.id=s.picking_type_id)

View File

@ -5,6 +5,7 @@ import time
from datetime import datetime
from flectra.tests.common import TransactionCase
from flectra.addons.account.tests.account_test_classes import AccountingTestCase
from flectra.tools import DEFAULT_SERVER_DATETIME_FORMAT
@ -181,7 +182,7 @@ class TestStockValuation(TransactionCase):
'company_id': po1.company_id.id,
})
eur_currency._compute_current_rate()
price_unit_usd_new_rate = po1.currency_id.compute(po1.order_line.price_unit, po1.company_id.currency_id, round=True)
price_unit_usd_new_rate = po1.currency_id.compute(po1.order_line.price_unit, po1.company_id.currency_id, round=False)
# the new price_unit is lower than th initial because of the rate's change
self.assertLess(price_unit_usd_new_rate, price_unit_usd)
@ -195,7 +196,7 @@ class TestStockValuation(TransactionCase):
wizard.process()
# the unit price of the stock move has been updated to the latest value
self.assertEquals(round(move1.price_unit), round(price_unit_usd_new_rate))
self.assertAlmostEqual(move1.price_unit, price_unit_usd_new_rate)
self.assertAlmostEqual(self.product1.stock_value, price_unit_usd_new_rate * 10, delta=0.1)
@ -271,3 +272,138 @@ class TestStockValuation(TransactionCase):
self.assertEqual(move2.price_unit, 100)
self.assertEqual(move2.product_qty, 5)
class TestStockValuationWithCOA(AccountingTestCase):
def setUp(self):
super(TestStockValuationWithCOA, self).setUp()
self.supplier_location = self.env.ref('stock.stock_location_suppliers')
self.stock_location = self.env.ref('stock.stock_location_stock')
self.partner_id = self.env.ref('base.res_partner_1')
self.product1 = self.env.ref('product.product_product_8')
Account = self.env['account.account']
self.stock_input_account = Account.create({
'name': 'Stock Input',
'code': 'StockIn',
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
})
self.stock_output_account = Account.create({
'name': 'Stock Output',
'code': 'StockOut',
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
})
self.stock_valuation_account = Account.create({
'name': 'Stock Valuation',
'code': 'Stock Valuation',
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
})
self.stock_journal = self.env['account.journal'].create({
'name': 'Stock Journal',
'code': 'STJTEST',
'type': 'general',
})
self.product1.categ_id.write({
'property_stock_account_input_categ_id': self.stock_input_account.id,
'property_stock_account_output_categ_id': self.stock_output_account.id,
'property_stock_valuation_account_id': self.stock_valuation_account.id,
'property_stock_journal': self.stock_journal.id,
})
def test_fifo_anglosaxon_return(self):
self.env.user.company_id.anglo_saxon_accounting = True
self.product1.product_tmpl_id.cost_method = 'fifo'
self.product1.product_tmpl_id.valuation = 'real_time'
self.product1.product_tmpl_id.invoice_policy = 'delivery'
price_diff_account = self.env['account.account'].create({
'name': 'price diff account',
'code': 'price diff account',
'user_type_id': self.env.ref('account.data_account_type_current_assets').id,
})
self.product1.property_account_creditor_price_difference = price_diff_account
# Receive 10@10 ; create the vendor bill
po1 = self.env['purchase.order'].create({
'partner_id': self.partner_id.id,
'order_line': [
(0, 0, {
'name': self.product1.name,
'product_id': self.product1.id,
'product_qty': 10.0,
'product_uom': self.product1.uom_po_id.id,
'price_unit': 10.0,
'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
}),
],
})
po1.button_confirm()
receipt_po1 = po1.picking_ids[0]
receipt_po1.move_lines.quantity_done = 10
receipt_po1.button_validate()
invoice_po1 = self.env['account.invoice'].create({
'partner_id': self.partner_id.id,
'purchase_id': po1.id,
'account_id': self.partner_id.property_account_payable_id.id,
'type': 'in_invoice',
})
invoice_po1.purchase_order_change()
invoice_po1.action_invoice_open()
# Receive 10@20 ; create the vendor bill
po2 = self.env['purchase.order'].create({
'partner_id': self.partner_id.id,
'order_line': [
(0, 0, {
'name': self.product1.name,
'product_id': self.product1.id,
'product_qty': 10.0,
'product_uom': self.product1.uom_po_id.id,
'price_unit': 20.0,
'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
}),
],
})
po2.button_confirm()
receipt_po2 = po2.picking_ids[0]
receipt_po2.move_lines.quantity_done = 10
receipt_po2.button_validate()
invoice_po2 = self.env['account.invoice'].create({
'partner_id': self.partner_id.id,
'purchase_id': po2.id,
'account_id': self.partner_id.property_account_payable_id.id,
'type': 'in_invoice',
})
invoice_po2.purchase_order_change()
invoice_po2.action_invoice_open()
# valuation of product1 should be 300
self.assertEqual(self.product1.stock_value, 300)
# return the second po
stock_return_picking = self.env['stock.return.picking']\
.with_context(active_ids=receipt_po2.ids, active_id=receipt_po2.ids[0])\
.create({})
stock_return_picking.product_return_moves.quantity = 10
stock_return_picking_action = stock_return_picking.create_returns()
return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
return_pick.move_lines[0].move_line_ids[0].qty_done = 10
return_pick.do_transfer()
# valuation of product1 should be 200 as the first items will be sent out
self.assertEqual(self.product1.stock_value, 200)
# create a credit note for po2
creditnote_po2 = self.env['account.invoice'].create({
'partner_id': self.partner_id.id,
'purchase_id': po2.id,
'account_id': self.partner_id.property_account_payable_id.id,
'type': 'in_refund',
})
creditnote_po2.purchase_order_change()
creditnote_po2.invoice_line_ids[0].quantity = 10
creditnote_po2.action_invoice_open()
# check the anglo saxon entries
price_diff_entry = self.env['account.move.line'].search([('account_id', '=', price_diff_account.id)])
self.assertEqual(price_diff_entry.credit, 100)

View File

@ -5,7 +5,7 @@
<div>
<strong>The ordered quantity has been updated.</strong>
<ul>
<li><t t-esc="line.order_id.product_id.display_name"/>:</li>
<li><t t-esc="line.product_id.display_name"/>:</li>
Ordered Quantity: <t t-esc="line.product_qty" /> -&gt; <t t-esc="float(product_qty)"/><br/>
<t t-if='line.order_id.product_id.type in ("consu", "product")'>
Received Quantity: <t t-esc="line.qty_received" /><br/>