Master bhumika 06072018

This commit is contained in:
Bhumika 2018-07-13 09:21:38 +00:00 committed by Parthiv Patel
parent 823c7409a5
commit 8e2d35fd5c
31 changed files with 355 additions and 80 deletions

View File

@ -33,7 +33,7 @@ class DeliveryCarrier(models.Model):
# Internals for shipping providers # # Internals for shipping providers #
# -------------------------------- # # -------------------------------- #
name = fields.Char(required=True) name = fields.Char(required=True, translate=True)
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
sequence = fields.Integer(help="Determine the display order", default=10) 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') # 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 # Create the sales order line
values = { values = {
'order_id': self.id, 'order_id': self.id,
'name': carrier.name, 'name': carrier.with_context(lang=self.partner_id.lang).name,
'product_uom_qty': 1, 'product_uom_qty': 1,
'product_uom': carrier.product_id.uom_id.id, 'product_uom': carrier.product_id.uom_id.id,
'product_id': carrier.product_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: if not line.product_id or not line.product_uom or not line.product_uom_qty:
return 0.0 return 0.0
line.product_qty = line.product_uom._compute_quantity(line.product_uom_qty, line.product_id.uom_id) 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): def send_to_shipper(self):
self.ensure_one() self.ensure_one()
res = self.carrier_id.send_shipping(self)[0] 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_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 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) 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) 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') self.free_delivery = self.env.ref('delivery.free_delivery_carrier')
# as the tests hereunder assume all the prices in USD, we must ensure # as the tests hereunder assume all the prices in USD, we must ensure
# that the company actually uses USD # 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): def test_00_delivery_cost(self):
# In order to test Carrier Cost # In order to test Carrier Cost

View File

@ -438,9 +438,14 @@ class EventRegistration(models.Model):
@api.multi @api.multi
def message_get_suggested_recipients(self): def message_get_suggested_recipients(self):
recipients = super(EventRegistration, self).message_get_suggested_recipients() 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: try:
for attendee in self: 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')) attendee._message_add_suggested_recipient(recipients, partner=attendee.partner_id, reason=_('Customer'))
elif attendee.email: elif attendee.email:
attendee._message_add_suggested_recipient(recipients, email=attendee.email, reason=_('Customer 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 dateutil.relativedelta import relativedelta
from flectra import api, fields, models, tools 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 = { _INTERVALS = {
'hours': lambda interval: relativedelta(hours=interval), 'hours': lambda interval: relativedelta(hours=interval),
@ -114,13 +119,52 @@ class EventMailScheduler(models.Model):
self.write({'mail_sent': True}) self.write({'mail_sent': True})
return 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 @api.model
def run(self, autocommit=False): def run(self, autocommit=False):
schedulers = self.search([('done', '=', False), ('scheduled_date', '<=', datetime.strftime(fields.datetime.now(), tools.DEFAULT_SERVER_DATETIME_FORMAT))]) schedulers = self.search([('done', '=', False), ('scheduled_date', '<=', datetime.strftime(fields.datetime.now(), tools.DEFAULT_SERVER_DATETIME_FORMAT))])
for scheduler in schedulers: for scheduler in schedulers:
scheduler.execute() try:
if autocommit: with self.env.cr.savepoint():
self.env.cr.commit() 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 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.") event_count = fields.Integer("Events", compute='_compute_event_count', help="Number of events the partner has participated.")
def _compute_event_count(self): def _compute_event_count(self):
if not self.user_has_groups('event.group_event_user'):
return
for partner in self: for partner in self:
partner.event_count = self.env['event.event'].search_count([('registration_ids.partner_id', 'child_of', partner.ids)]) 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;"/> <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> </span>
<h5 t-field="o.event_id.name"/> <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>
<div class="row"> <div class="row">
<div class="col-xs-12 text-center" id="o_event_name"> <div class="col-xs-12 text-center" id="o_event_name">

View File

@ -41,5 +41,12 @@
] ]
</field> </field>
</record> </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> </data>
</flectra> </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_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_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,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,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_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 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="name">view.res.partner.form.event.inherited</field>
<field name="model">res.partner</field> <field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/> <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"> <field name="arch" type="xml">
<div name="button_box" position="inside"> <div name="button_box" position="inside">
<button class="oe_stat_button" <button class="oe_stat_button"
type="object" type="object"
icon="fa-ticket" icon="fa-ticket"
groups="event.group_event_user"
name="action_event_view" attrs="{'invisible': [('event_count','=', 0)]}"> name="action_event_view" attrs="{'invisible': [('event_count','=', 0)]}">
<field string="Events" name="event_count" widget="statinfo"/> <field string="Events" name="event_count" widget="statinfo"/>
</button> </button>

View File

@ -27,6 +27,7 @@ this event.
'data/event_sale_data.xml', 'data/event_sale_data.xml',
'report/event_event_templates.xml', 'report/event_event_templates.xml',
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'security/event_security.xml',
'wizard/event_edit_registration.xml', 'wizard/event_edit_registration.xml',
], ],
'demo': ['data/event_demo.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 order line has a product_uom_qty attribute that will be the number of
registrations linked to this line. This method update existing registrations registrations linked to this line. This method update existing registrations
and create new one for missing one. """ 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')]) registrations = Registration.search([('sale_order_line_id', 'in', self.ids), ('state', '!=', 'cancel')])
for so_line in self.filtered('event_id'): for so_line in self.filtered('event_id'):
existing_registrations = registrations.filtered(lambda self: self.sale_order_line_id.id == so_line.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) name = fields.Char(compute="_compute_vehicle_name", store=True)
active = fields.Boolean('Active', default=True, track_visibility="onchange") active = fields.Boolean('Active', default=True, track_visibility="onchange")
company_id = fields.Many2one('res.company', 'Company') 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) 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') 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_fuel = fields.One2many('fleet.vehicle.log.fuel', 'vehicle_id', 'Fuel Logs')
log_services = fields.One2many('fleet.vehicle.log.services', 'vehicle_id', 'Services 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!') ('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): def _compute_vehicle_name(self):
for record in 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): def _get_odometer(self):
FleetVehicalOdometer = self.env['fleet.vehicle.odometer'] 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 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.""" the target value being reached, the goal is set as failed."""
goals_by_definition = {} 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) goals_by_definition.setdefault(goal.definition_id, []).append(goal)
for definition, goals in goals_by_definition.items(): for definition, goals in goals_by_definition.items():

View File

@ -677,7 +677,7 @@ class GoogleCalendar(models.AbstractModel):
try: try:
all_event_from_google = self.get_event_synchro_dict(lastSync=lastSync) all_event_from_google = self.get_event_synchro_dict(lastSync=lastSync)
except requests.HTTPError as e: 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. # 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.cr.rollback()
self.env.user.write({'google_calendar_last_sync_date': False}) self.env.user.write({'google_calendar_last_sync_date': False})
@ -842,7 +842,7 @@ class GoogleCalendar(models.AbstractModel):
try: try:
# if already deleted from gmail or never created # if already deleted from gmail or never created
recs.delete_an_event(current_event[0]) 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,): if e.response.status_code in (401, 410,):
pass pass
else: else:

View File

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

View File

@ -2,10 +2,12 @@
# 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 collections import OrderedDict from collections import OrderedDict
from operator import itemgetter
from flectra import http, _ from flectra import http, _
from flectra.http import request from flectra.http import request
from flectra.addons.portal.controllers.portal import get_records_pager, CustomerPortal, pager as portal_pager 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 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) @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): 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() values = self._prepare_portal_layout_values()
searchbar_sortings = { searchbar_sortings = {
'date': {'label': _('Newest'), 'order': 'create_date desc'}, 'date': {'label': _('Newest'), 'order': 'create_date desc'},
@ -95,6 +98,10 @@ class CustomerPortal(CustomerPortal):
'stage': {'input': 'stage', 'label': _('Search in Stages')}, 'stage': {'input': 'stage', 'label': _('Search in Stages')},
'all': {'input': 'all', 'label': _('Search in All')}, '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) # extends filterby criteria with project (criteria name is the project id)
# Note: portal users can't view projects they don't follow # Note: portal users can't view projects they don't follow
projects = request.env['project.project'].sudo().search([('privacy_visibility', '=', 'portal')]) projects = request.env['project.project'].sudo().search([('privacy_visibility', '=', 'portal')])
@ -136,28 +143,37 @@ class CustomerPortal(CustomerPortal):
# pager # pager
pager = portal_pager( pager = portal_pager(
url="/my/tasks", 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, total=task_count,
page=page, page=page,
step=self._items_per_page step=self._items_per_page
) )
# content according to pager and archive selected # 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']) 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] 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({ values.update({
'date': date_begin, 'date': date_begin,
'date_end': date_end, 'date_end': date_end,
'projects': projects, 'projects': projects,
'tasks': tasks, 'tasks': tasks,
'grouped_tasks': grouped_tasks,
'page_name': 'task', 'page_name': 'task',
'archive_groups': archive_groups, 'archive_groups': archive_groups,
'default_url': '/my/tasks', 'default_url': '/my/tasks',
'pager': pager, 'pager': pager,
'searchbar_sortings': searchbar_sortings, 'searchbar_sortings': searchbar_sortings,
'searchbar_groupby': searchbar_groupby,
'searchbar_inputs': searchbar_inputs, 'searchbar_inputs': searchbar_inputs,
'search_in': search_in, 'search_in': search_in,
'sortby': sortby, 'sortby': sortby,
'groupby': groupby,
'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())), 'searchbar_filters': OrderedDict(sorted(searchbar_filters.items())),
'filterby': filterby, 'filterby': filterby,
}) })

View File

@ -4,7 +4,7 @@
from lxml import etree from lxml import etree
from flectra import api, fields, models, tools, SUPERUSER_ID, _ 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 flectra.tools.safe_eval import safe_eval
from datetime import timedelta, date 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_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) 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) 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_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") 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) 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: for task in self:
task.subtask_count = self.search_count([('id', 'child_of', task.id), ('id', '!=', task.id)]) 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') @api.constrains('parent_id')
def _check_subtask_project(self): def _check_subtask_project(self):
for task in self: for task in self:
@ -796,7 +802,7 @@ class Task(models.Model):
groups = [new_group] + groups groups = [new_group] + groups
for group_name, group_method, group_data in groups: for group_name, group_method, group_data in groups:
if group_name in ['customer', 'portal']: if group_name == 'customer':
continue continue
group_data['has_button_access'] = True group_data['has_button_access'] = True
@ -874,12 +880,12 @@ class Task(models.Model):
@api.multi @api.multi
def message_get_suggested_recipients(self): def message_get_suggested_recipients(self):
recipients = super(Task, self).message_get_suggested_recipients() recipients = super(Task, self).message_get_suggested_recipients()
for task in self.filtered('partner_id'): for task in self:
reason = _('Customer Email') if task.partner_id.email else _('Customer')
if task.partner_id: 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) task._message_add_suggested_recipient(recipients, partner=task.partner_id, reason=reason)
elif task.email_from: 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 return recipients
@api.multi @api.multi
@ -926,6 +932,20 @@ class Task(models.Model):
'type': 'ir.actions.act_window' '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): class AccountAnalyticAccount(models.Model):
_inherit = 'account.analytic.account' _inherit = 'account.analytic.account'

View File

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

View File

@ -413,7 +413,7 @@
<sheet string="Task"> <sheet string="Task">
<div class="oe_button_box" name="button_box"> <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 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)]}" attrs="{'invisible' : [('parent_id', '!=', False)]}"
context="{'project_id': subtask_project_id, 'name': name, 'partner_id': partner_id}" groups="project.group_subtask_project"> 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"/> <field string="Sub-tasks" name="subtask_count" widget="statinfo"/>

View File

@ -1,7 +1,7 @@
# -*- 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 datetime import datetime from datetime import datetime, time
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from flectra import fields from flectra import fields

View File

@ -5,7 +5,7 @@
<!--Email template --> <!--Email template -->
<record id="email_template_edi_purchase" model="mail.template"> <record id="email_template_edi_purchase" model="mail.template">
<field name="name">RFQ - Send by Email</field> <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="subject">${object.company_id.name} 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="purchase.model_purchase_order"/> <field name="model_id" ref="purchase.model_purchase_order"/>
@ -46,7 +46,7 @@ from ${object.company_id.name}.
<!--Email template --> <!--Email template -->
<record id="email_template_edi_purchase_done" model="mail.template"> <record id="email_template_edi_purchase_done" model="mail.template">
<field name="name">Purchase Order - Send by Email</field> <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="subject">${object.company_id.name} 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="purchase.model_purchase_order"/> <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="auto_delete" eval="True"/>
<field name="body_html"><![CDATA[<html> <field name="body_html"><![CDATA[<html>
<head></head> <head></head>
% 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" summary="o_mail_notification"> <table border="0" width="100%" cellpadding="0" bgcolor="#ededed" style="padding: 20px; background-color: #ededed" summary="o_mail_notification">
<tbody> <tbody>
@ -101,7 +102,7 @@ from ${object.company_id.name}.
</span> </span>
</td> </td>
<td valign="middle" align="right"> <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> </td>
</tr> </tr>
</table> </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;"> <table width="590" border="0" cellpadding="0" bgcolor="#875A7B" style="min-width: 590px; background-color: rgb(135,90,123); padding: 20px;">
<tr> <tr>
<td valign="middle" align="left" style="color: #fff; padding-top: 10px; padding-bottom: 10px; font-size: 12px;"> <td valign="middle" align="left" style="color: #fff; padding-top: 10px; padding-bottom: 10px; font-size: 12px;">
${user.company_id.name}<br/> ${company.name}<br/>
${user.company_id.phone or ''} ${company.phone or ''}
</td> </td>
<td valign="middle" align="right" style="color: #fff; padding-top: 10px; padding-bottom: 10px; font-size: 12px;"> <td valign="middle" align="right" style="color: #fff; padding-top: 10px; padding-bottom: 10px; font-size: 12px;">
% if user.company_id.email: % if company.email:
<a href="mailto:${user.company_id.email}" style="text-decoration:none; color: white;">${user.company_id.email}</a><br/> <a href="mailto:${company.email}" style="text-decoration:none; color: white;">${company.email}</a><br/>
% endif % endif
% if user.company_id.website: % if company.website:
<a href="${user.company_id.website}" style="text-decoration:none; color: white;"> <a href="${company.website}" style="text-decoration:none; color: white;">
${user.company_id.website} ${company.website}
</a> </a>
% endif % endif
</td> </td>

View File

@ -6,3 +6,6 @@
warehouse = self.browse(ref('stock.' + wh_ref)) warehouse = self.browse(ref('stock.' + wh_ref))
#Force the rewriting of route and rule #Force the rewriting of route and rule
warehouse.write({'buy_to_resupply': True}) 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 #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) 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'] 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: if valuation_stock_move:
valuation_price_unit_total = 0 valuation_price_unit_total = 0
valuation_total_qty = 0 valuation_total_qty = 0

View File

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

View File

@ -27,7 +27,7 @@ class StockMove(models.Model):
_inherit = 'stock.move' _inherit = 'stock.move'
purchase_line_id = fields.Many2one('purchase.order.line', 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_line_id = fields.Many2one('purchase.order.line',
'Created Purchase Order Line', ondelete='set null', readonly=True, copy=False) '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 join res_partner partner on s.partner_id = partner.id
left join product_product p on (l.product_id=p.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 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 u on (u.id=l.product_uom)
left join product_uom u2 on (u2.id=t.uom_id) left join product_uom u2 on (u2.id=t.uom_id)
left join stock_picking_type spt on (spt.id=s.picking_type_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 datetime import datetime
from flectra.tests.common import TransactionCase from flectra.tests.common import TransactionCase
from flectra.addons.account.tests.account_test_classes import AccountingTestCase
from flectra.tools import DEFAULT_SERVER_DATETIME_FORMAT from flectra.tools import DEFAULT_SERVER_DATETIME_FORMAT
@ -181,7 +182,7 @@ class TestStockValuation(TransactionCase):
'company_id': po1.company_id.id, 'company_id': po1.company_id.id,
}) })
eur_currency._compute_current_rate() 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 # 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) self.assertLess(price_unit_usd_new_rate, price_unit_usd)
@ -195,7 +196,7 @@ class TestStockValuation(TransactionCase):
wizard.process() wizard.process()
# the unit price of the stock move has been updated to the latest value # 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) 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.price_unit, 100)
self.assertEqual(move2.product_qty, 5) 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> <div>
<strong>The ordered quantity has been updated.</strong> <strong>The ordered quantity has been updated.</strong>
<ul> <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/> 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")'> <t t-if='line.order_id.product_id.type in ("consu", "product")'>
Received Quantity: <t t-esc="line.qty_received" /><br/> Received Quantity: <t t-esc="line.qty_received" /><br/>