前言(MTO与MTS)
MTO:基于销售订单的生产,即销售订单需要多少就生产多少。在odoo13中,销售下单确认后就直接根据销售订单明细行生成制造订单。如果在增加销售订单,相同的产品也会重新创建一个制造订单。
MTS:基于库存的生产,即库存缺多少就生产多少。在odoo13中,只要制造订单没有开始安排生产,相同的产品会在原有制造订单上更新生产数量。
现实需求:
一般的中小企业无法做到真正的零库存,即只能以MTS方式生产,但同时又希望制造订单可以跟踪到销售订单。
在odoo14中,即使MTS,在一切配置正确的情况下,也是一个销售订单创建一个制造订单,和MTO几乎是一样的,而且还可以减去多余库存,因此在odoo14中,MTO是默认被归档的。异常场景(系统已经有了某产品的很多销售订单,但是没有配置正确的路线,导致系统无法创建制造订单,一旦配置正确,系统将会根据库存需求创建一个制造订单)。
制造订单与销售订单的关联
odoo14中,MTS几乎是达到了MTO的效果,唯一的缺点就是制造订单与销售订单断开了联系,源文档都是通过订货规则。
数据流
数据流转过程中,销售明细确认后对应生成了出库明细,如果库存缺货,stock.move将会根据订货规则创建制造订单,但是stock.move向订货规则流向时,只将产品、数量、位置等信息传递下去,没有将销售明细(sale_line_id)传递到生成,导致制造订单与销售订单关系断裂。
解决方案
在准备制造订单数据结构时,将sale_line_id、sale_id、partner_id等于销售订单相关的信息都可以传递到制造订单。
效果
在这里插入代码片
odoo.define('account.section_and_note_backend', function (require) {
// The goal of this file is to contain JS hacks related to allowing
// section and note on sale order and invoice.
// [UPDATED] now also allows configuring products on sale order.
// We create a custom widget because this is the cleanest way to do it:
// to be sure this custom code will only impact selected fields having the widget
// and not applied to any other existing ListRenderer.
"use strict";
var FieldChar = require('web.basic_fields').FieldChar;
var FieldOne2Many = require('web.relational_fields').FieldOne2Many;
var fieldRegistry = require('web.field_registry');
var ListFieldText = require('web.basic_fields').ListFieldText;
var ListRenderer = require('web.ListRenderer');
var SectionAndNoteListRenderer = ListRenderer.extend({
/**
* We want section and note to take the whole line (except handle and trash)
* to look better and to hide the unnecessary fields.
*
* @override
*/
_renderBodyCell: function (record, node, index, options) {
var $cell = this._super.apply(this, arguments);
var isSection = record.data.display_type === 'line_section';
var isNote = record.data.display_type === 'line_note';
if (isSection || isNote) {
if (node.attrs.widget === "handle") {
return $cell;
} else if (node.attrs.name === "name") {
var nbrColumns = this._getNumberOfCols();
if (this.handleField) {
nbrColumns--;
}
if (this.addTrashIcon) {
nbrColumns--;
}
$cell.attr('colspan', nbrColumns);
} else {
$cell.removeClass('o_invisible_modifier');
return $cell.addClass('o_hidden');
}
}
return $cell;
},
/**
* We add the o_is_{display_type} class to allow custom behaviour both in JS and CSS.
*
* @override
*/
_renderRow: function (record, index) {
var $row = this._super.apply(this, arguments);
if (record.data.display_type) {
$row.addClass('o_is_' + record.data.display_type);
}
return $row;
},
/**
* We want to add .o_section_and_note_list_view on the table to have stronger CSS.
*
* @override
* @private
*/
_renderView: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
self.$('.o_list_table').addClass('o_section_and_note_list_view');
});
}
});
var SectionAndNoteFieldOne2Many = FieldOne2Many.extend({
/**
* We want to use our custom renderer for the list.
*
* @override
*/
_getRenderer: function () {
if (this.view.arch.tag === 'tree') {
return SectionAndNoteListRenderer;
}
return this._super.apply(this, arguments);
},
});
fieldRegistry.add('section_and_note_one2many', SectionAndNoteFieldOne2Many);
return SectionAndNoteListRenderer;
});