},
}
- def copy(self, cr, uid, id, default=None, context=None):
- if not default:
- default = {}
- default.update({
- 'date_order': fields.datetime.now(),
- 'state': 'draft',
- 'invoice_ids': [],
- 'date_confirm': False,
- 'client_order_ref': '',
- 'name': self.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
- 'procurement_group_id': False,
- })
- return super(sale_order, self).copy(cr, uid, id, default, context=context)
-
def _amount_line_tax(self, cr, uid, line, context=None):
val = 0.0
for c in self.pool.get('account.tax').compute_all(cr, uid, line.tax_id, line.price_unit * (1-(line.discount or 0.0)/100.0), line.product_uom_qty, line.product_id, line.order_id.partner_id)['taxes']:
return None
_columns = {
- 'name': fields.char('Order Reference', size=64, required=True,
+ 'name': fields.char('Order Reference', required=True, copy=False,
readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, select=True),
- 'origin': fields.char('Source Document', size=64, help="Reference of the document that generated this sales order request."),
- 'client_order_ref': fields.char('Reference/Description', size=64),
+ 'origin': fields.char('Source Document', help="Reference of the document that generated this sales order request."),
+ 'client_order_ref': fields.char('Reference/Description', copy=False),
'state': fields.selection([
('draft', 'Draft Quotation'),
('sent', 'Quotation Sent'),
('shipping_except', 'Shipping Exception'),
('invoice_except', 'Invoice Exception'),
('done', 'Done'),
- ], 'Status', readonly=True, help="Gives the status of the quotation or sales order.\
+ ], 'Status', readonly=True, copy=False, help="Gives the status of the quotation or sales order.\
\nThe exception status is automatically set when a cancel operation occurs \
in the invoice validation (Invoice Exception) or in the picking list process (Shipping Exception).\nThe 'Waiting Schedule' status is set when the invoice is confirmed\
but waiting for the scheduler to run on the order date.", select=True),
- 'date_order': fields.datetime('Date', required=True, readonly=True, select=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}),
+ 'date_order': fields.datetime('Date', required=True, readonly=True, select=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, copy=False),
'create_date': fields.datetime('Creation Date', readonly=True, select=True, help="Date on which sales order is created."),
- 'date_confirm': fields.date('Confirmation Date', readonly=True, select=True, help="Date on which sales order is confirmed."),
+ 'date_confirm': fields.date('Confirmation Date', readonly=True, select=True, help="Date on which sales order is confirmed.", copy=False),
'user_id': fields.many2one('res.users', 'Salesperson', states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, select=True, track_visibility='onchange'),
'partner_id': fields.many2one('res.partner', 'Customer', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, required=True, change_default=True, select=True, track_visibility='always'),
'partner_invoice_id': fields.many2one('res.partner', 'Invoice Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="Invoice address for current sales order."),
'currency_id': fields.related('pricelist_id', 'currency_id', type="many2one", relation="res.currency", string="Currency", readonly=True, required=True),
'project_id': fields.many2one('account.analytic.account', 'Contract / Analytic', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="The analytic account related to a sales order."),
- 'order_line': fields.one2many('sale.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}),
- 'invoice_ids': fields.many2many('account.invoice', 'sale_order_invoice_rel', 'order_id', 'invoice_id', 'Invoices', readonly=True, help="This is the list of invoices that have been generated for this sales order. The same sales order may have been invoiced in several times (by line for example)."),
+ 'order_line': fields.one2many('sale.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, copy=True),
+ 'invoice_ids': fields.many2many('account.invoice', 'sale_order_invoice_rel', 'order_id', 'invoice_id', 'Invoices', readonly=True, copy=False, help="This is the list of invoices that have been generated for this sales order. The same sales order may have been invoiced in several times (by line for example)."),
'invoiced_rate': fields.function(_invoiced_rate, string='Invoiced Ratio', type='float'),
'invoiced': fields.function(_invoiced, string='Paid',
fnct_search=_invoiced_search, type='boolean', help="It indicates that an invoice has been paid."),
'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
'company_id': fields.many2one('res.company', 'Company'),
'section_id': fields.many2one('crm.case.section', 'Sales Team'),
- 'procurement_group_id': fields.many2one('procurement.group', 'Procurement group'),
+ 'procurement_group_id': fields.many2one('procurement.group', 'Procurement group', copy=False),
}
_defaults = {
value = {
'currency_id': self.pool.get('product.pricelist').browse(cr, uid, pricelist_id, context=context).currency_id.id
}
- if not order_lines:
+ if not order_lines or order_lines == [(6, 0, [])]:
return {'value': value}
warning = {
'title': _('Pricelist Warning!'),
context_lang.update({'lang': partner_lang})
return self.pool.get('res.users').browse(cr, uid, uid, context=context_lang).company_id.sale_note
+ def onchange_delivery_id(self, cr, uid, ids, company_id, partner_id, delivery_id, fiscal_position, context=None):
+ r = {'value': {}}
+ if not fiscal_position:
+ if not company_id:
+ company_id = self._get_default_company(cr, uid, context=context)
+ fiscal_position = self.pool['account.fiscal.position'].get_fiscal_position(cr, uid, company_id, partner_id, delivery_id, context=context)
+ if fiscal_position:
+ r['value']['fiscal_position'] = fiscal_position
+ return r
+
def onchange_partner_id(self, cr, uid, ids, part, context=None):
if not part:
return {'value': {'partner_invoice_id': False, 'partner_shipping_id': False, 'payment_term': False, 'fiscal_position': False}}
addr = self.pool.get('res.partner').address_get(cr, uid, [part.id], ['delivery', 'invoice', 'contact'])
pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False
payment_term = part.property_payment_term and part.property_payment_term.id or False
- fiscal_position = part.property_account_position and part.property_account_position.id or False
dedicated_salesman = part.user_id and part.user_id.id or uid
val = {
'partner_invoice_id': addr['invoice'],
'partner_shipping_id': addr['delivery'],
'payment_term': payment_term,
- 'fiscal_position': fiscal_position,
'user_id': dedicated_salesman,
}
+ delivery_onchange = self.onchange_delivery_id(cr, uid, ids, False, part.id, addr['delivery'], False, context=context)
+ val.update(delivery_onchange['value'])
if pricelist:
val['pricelist_id'] = pricelist
sale_note = self.get_salenote(cr, uid, ids, part.id, context=context)
def create(self, cr, uid, vals, context=None):
if context is None:
- context = {}
+ context = {}
if vals.get('name', '/') == '/':
vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'sale.order') or '/'
- if vals.get('partner_id') and any(f not in vals for f in ['partner_invoice_id', 'partner_shipping_id', 'pricelist_id']):
- defaults = self.onchange_partner_id(cr, uid, [], vals['partner_id'], context)['value']
+ if vals.get('partner_id') and any(f not in vals for f in ['partner_invoice_id', 'partner_shipping_id', 'pricelist_id', 'fiscal_position']):
+ defaults = self.onchange_partner_id(cr, uid, [], vals['partner_id'], context=context)['value']
+ if not vals.get('fiscal_position') and vals.get('partner_shipping_id'):
+ delivery_onchange = self.onchange_delivery_id(cr, uid, [], vals.get('company_id'), None, vals['partner_id'], vals.get('partner_shipping_id'), context=context)
+ defaults.update(delivery_onchange['value'])
vals = dict(defaults, **vals)
- context.update({'mail_create_nolog': True})
- new_id = super(sale_order, self).create(cr, uid, vals, context=context)
- self.message_post(cr, uid, [new_id], body=_("Quotation created"), context=context)
+ ctx = dict(context or {}, mail_create_nolog=True)
+ new_id = super(sale_order, self).create(cr, uid, vals, context=ctx)
+ self.message_post(cr, uid, [new_id], body=_("Quotation created"), context=ctx)
return new_id
def button_dummy(self, cr, uid, ids, context=None):
This function prints the sales order and mark it as sent, so that we can see more easily the next step of the workflow
'''
assert len(ids) == 1, 'This option should only be used for a single id at a time'
- self.signal_quotation_sent(cr, uid, ids)
+ self.signal_workflow(cr, uid, ids, 'quotation_sent')
return self.pool['report'].get_action(cr, uid, ids, 'sale.report_saleorder', context=context)
def manual_invoice(self, cr, uid, ids, context=None):
# create invoices through the sales orders' workflow
inv_ids0 = set(inv.id for sale in self.browse(cr, uid, ids, context) for inv in sale.invoice_ids)
- self.signal_manual_invoice(cr, uid, ids)
+ self.signal_workflow(cr, uid, ids, 'manual_invoice')
inv_ids1 = set(inv.id for sale in self.browse(cr, uid, ids, context) for inv in sale.invoice_ids)
# determine newly created invoices
new_inv_ids = list(inv_ids1 - inv_ids0)
invoice = self.pool.get('account.invoice')
obj_sale_order_line = self.pool.get('sale.order.line')
partner_currency = {}
- if context is None:
- context = {}
# If date was specified, use it as date invoiced, usefull when invoices are generated this month and put the
# last day of the last month as invoice date
if date_invoice:
- context['date_invoice'] = date_invoice
+ context = dict(context or {}, date_invoice=date_invoice)
for o in self.browse(cr, uid, ids, context=context):
currency_id = o.pricelist_id.currency_id.id
if (o.partner_id.id in partner_currency) and (partner_currency[o.partner_id.id] <> currency_id):
origin_ref += (o.origin or o.name) + '|'
self.write(cr, uid, [o.id], {'state': 'progress'})
cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (o.id, res))
+ self.invalidate_cache(cr, uid, ['invoice_ids'], [o.id], context=context)
#remove last '|' in invoice_ref
if len(invoice_ref) >= 1:
invoice_ref = invoice_ref[:-1]
invoice_ids.append(res)
self.write(cr, uid, [order.id], {'state': 'progress'})
cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (order.id, res))
+ self.invalidate_cache(cr, uid, ['invoice_ids'], [order.id], context=context)
return res
def action_invoice_cancel(self, cr, uid, ids, context=None):
raise osv.except_osv(
_('Cannot cancel this sales order!'),
_('First cancel all invoices attached to this sales order.'))
- for r in self.read(cr, uid, ids, ['invoice_ids']):
- account_invoice_obj.signal_invoice_cancel(cr, uid, r['invoice_ids'])
+ inv.signal_workflow('invoice_cancel')
sale_order_line_obj.write(cr, uid, [l.id for l in sale.order_line],
{'state': 'cancel'})
self.write(cr, uid, ids, {'state': 'cancel'})
def action_button_confirm(self, cr, uid, ids, context=None):
assert len(ids) == 1, 'This option should only be used for a single id at a time.'
- self.signal_order_confirm(cr, uid, ids)
+ self.signal_workflow(cr, uid, ids, 'order_confirm')
return True
def action_wait(self, cr, uid, ids, context=None):
def procurement_needed(self, cr, uid, ids, context=None):
#when sale is installed only, there is no need to create procurements, that's only
- #further installed modules (project_mrp, sale_stock) that will change this.
+ #further installed modules (sale_service, sale_stock) that will change this.
sale_line_obj = self.pool.get('sale.order.line')
res = []
for order in self.browse(cr, uid, ids, context=context):
procurement_obj.check(cr, uid, [x.id for x in line.procurement_ids if x.state not in ['cancel', 'done']])
line.refresh()
#run again procurement that are in exception in order to trigger another move
- proc_ids += [x.id for x in line.procurement_ids if x.state == 'exception']
+ proc_ids += [x.id for x in line.procurement_ids if x.state in ('exception', 'cancel')]
elif sale_line_obj.need_procurement(cr, uid, [line.id], context=context):
if (line.state == 'done') or not line.product_id:
continue
order.write(val)
return True
- # if mode == 'finished':
- # returns True if all lines are done, False otherwise
- # if mode == 'canceled':
- # returns True if there is at least one canceled line, False otherwise
- def test_state(self, cr, uid, ids, mode, *args):
- assert mode in ('finished', 'canceled'), _("invalid mode for test_state")
- finished = True
- canceled = False
- write_done_ids = []
- write_cancel_ids = []
- for order in self.browse(cr, uid, ids, context={}):
-
- #TODO: Need to rethink what happens when cancelling
- for line in order.order_line:
- states = [x.state for x in line.procurement_ids]
- cancel = states and all([x == 'cancel' for x in states])
- doneorcancel = all([x in ('done', 'cancel') for x in states])
- if cancel:
- canceled = True
- if line.state != 'exception':
- write_cancel_ids.append(line.id)
- if not doneorcancel:
- finished = False
- if doneorcancel and not cancel:
- write_done_ids.append(line.id)
-
- if write_done_ids:
- self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'})
- if write_cancel_ids:
- self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'exception'})
-
- if mode == 'finished':
- return finished
- elif mode == 'canceled':
- return canceled
-
-
- def procurement_lines_get(self, cr, uid, ids, *args):
- res = []
- for order in self.browse(cr, uid, ids, context={}):
- for line in order.order_line:
- res += [x.id for x in line.procurement_ids]
- return res
+
def onchange_fiscal_position(self, cr, uid, ids, fiscal_position, order_lines, context=None):
'''Update taxes of order lines for each line where a product is defined
order_line.append(line)
return {'value': {'order_line': order_line}}
+ def test_procurements_done(self, cr, uid, ids, context=None):
+ for sale in self.browse(cr, uid, ids, context=context):
+ for line in sale.order_line:
+ if not all([x.state == 'done' for x in line.procurement_ids]):
+ return False
+ return True
+
+ def test_procurements_except(self, cr, uid, ids, context=None):
+ for sale in self.browse(cr, uid, ids, context=context):
+ for line in sale.order_line:
+ if any([x.state == 'cancel' for x in line.procurement_ids]):
+ return True
+ return False
+
# TODO add a field price_unit_uos
# - update it on change product and unit price
def need_procurement(self, cr, uid, ids, context=None):
#when sale is installed only, there is no need to create procurements, that's only
- #further installed modules (project_mrp, sale_stock) that will change this.
+ #further installed modules (sale_service, sale_stock) that will change this.
+ prod_obj = self.pool.get('product.product')
+ for line in self.browse(cr, uid, ids, context=context):
+ if prod_obj.need_procurement(cr, uid, [line.product_id.id], context=context):
+ return True
return False
def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
'name': fields.text('Description', required=True, readonly=True, states={'draft': [('readonly', False)]}),
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of sales order lines."),
'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], change_default=True, readonly=True, states={'draft': [('readonly', False)]}, ondelete='restrict'),
- 'invoice_lines': fields.many2many('account.invoice.line', 'sale_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
+ 'invoice_lines': fields.many2many('account.invoice.line', 'sale_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True, copy=False),
'invoiced': fields.function(_fnct_line_invoiced, string='Invoiced', type='boolean',
store={
'account.invoice': (_order_lines_from_invoice, ['state'], 10),
'product_uos': fields.many2one('product.uom', 'Product UoS'),
'discount': fields.float('Discount (%)', digits_compute= dp.get_precision('Discount'), readonly=True, states={'draft': [('readonly', False)]}),
'th_weight': fields.float('Weight', readonly=True, states={'draft': [('readonly', False)]}),
- 'state': fields.selection([('cancel', 'Cancelled'),('draft', 'Draft'),('confirmed', 'Confirmed'),('exception', 'Exception'),('done', 'Done')], 'Status', required=True, readonly=True,
+ 'state': fields.selection(
+ [('cancel', 'Cancelled'),('draft', 'Draft'),('confirmed', 'Confirmed'),('exception', 'Exception'),('done', 'Done')],
+ 'Status', required=True, readonly=True, copy=False,
help='* The \'Draft\' status is set when the related sales order in draft status. \
\n* The \'Confirmed\' status is set when the related sales order is confirmed. \
\n* The \'Exception\' status is set when the related sales order is set as exception. \
'delay': 0.0,
}
+
+
def _get_line_qty(self, cr, uid, line, context=None):
if line.product_uos:
return line.product_uos_qty or 0.0
values = dict(defaults, **values)
return super(sale_order_line, self).create(cr, uid, values, context=context)
- def copy_data(self, cr, uid, id, default=None, context=None):
- if not default:
- default = {}
- default.update({'state': 'draft', 'invoice_lines': [], 'procurement_ids': []})
- return super(sale_order_line, self).copy_data(cr, uid, id, default, context=context)
-
def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
uom=False, qty_uos=0, uos=False, name='', partner_id=False,
lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False, context=None):
context = context or {}
if context.get('default_model') == 'sale.order' and context.get('default_res_id') and context.get('mark_so_as_sent'):
context = dict(context, mail_post_autofollow=True)
- self.pool.get('sale.order').signal_quotation_sent(cr, uid, [context['default_res_id']])
+ self.pool.get('sale.order').signal_workflow(cr, uid, [context['default_res_id']], 'quotation_sent')
return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context)
'sale_line_id': fields.many2one('sale.order.line', string='Sale Order Line'),
}
+ def write(self, cr, uid, ids, vals, context=None):
+ if isinstance(ids, (int, long)):
+ ids = [ids]
+ res = super(procurement_order, self).write(cr, uid, ids, vals, context=context)
+ from openerp import workflow
+ if vals.get('state') in ['done', 'cancel', 'exception']:
+ for proc in self.browse(cr, uid, ids, context=context):
+ if proc.sale_line_id and proc.sale_line_id.order_id:
+ order_id = proc.sale_line_id.order_id.id
+ if self.pool.get('sale.order').test_procurements_done(cr, uid, [order_id], context=context):
+ workflow.trg_validate(uid, 'sale.order', order_id, 'ship_end', cr)
+ if self.pool.get('sale.order').test_procurements_except(cr, uid, [order_id], context=context):
+ workflow.trg_validate(uid, 'sale.order', order_id, 'ship_except', cr)
+ return res
class product_product(osv.Model):
_inherit = 'product.product'
product_id: SaleOrderLine.search_count(cr,uid, [('product_id', '=', product_id)], context=context)
for product_id in ids
}
+
_columns = {
'sales_count': fields.function(_sales_count, string='# Sales', type='integer'),
}
+class product_template(osv.Model):
+ _inherit = 'product.template'
+
+ def _sales_count(self, cr, uid, ids, field_name, arg, context=None):
+ res = dict.fromkeys(ids, 0)
+ for template in self.browse(cr, uid, ids, context=context):
+ res[template.id] = sum([p.sales_count for p in template.product_variant_ids])
+ return res
+
+ def action_view_sales(self, cr, uid, ids, context=None):
+ act_obj = self.pool.get('ir.actions.act_window')
+ mod_obj = self.pool.get('ir.model.data')
+ product_ids = []
+ for template in self.browse(cr, uid, ids, context=context):
+ product_ids += [x.id for x in template.product_variant_ids]
+ result = mod_obj.xmlid_to_res_id(cr, uid, 'sale.action_order_line_product_tree',raise_if_not_found=True)
+ result = act_obj.read(cr, uid, [result], context=context)[0]
+ result['domain'] = "[('product_id','in',[" + ','.join(map(str, product_ids)) + "])]"
+ return result
+
+
+ _columns = {
+ 'sales_count': fields.function(_sales_count, string='# Sales', type='integer'),
+
+ }
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: