first commit

This commit is contained in:
root
2026-03-14 09:42:12 +00:00
commit 0adbd20c2c
10991 changed files with 1646955 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

View File

@@ -0,0 +1,18 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr ""
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""

View File

@@ -0,0 +1,20 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr "Produccions"
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr "Produccions"
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""
"No podeu restablir la producció \"%(production)s\" a esborrany perquè s'ha "
"generat a partir d'una venda."

View File

@@ -0,0 +1,18 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr ""
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""

View File

@@ -0,0 +1,20 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr "Produktionsaufträge"
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr "Produktionsaufträge"
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""
"Produktionsauftrag \"%(production)s\" kann nicht auf Entwurf zurück gesetzt "
"werden, weil er durch einen Verkauf generiert wurde."

View File

@@ -0,0 +1,20 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr "Producciones"
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr "Producciones"
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""
"No puede restablecer la producción \"%(production)s\" a borrador ya que ha "
"sido generada por una venta."

View File

@@ -0,0 +1,18 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr ""
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""

View File

@@ -0,0 +1,18 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr ""
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""

View File

@@ -0,0 +1,18 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr ""
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""

View File

@@ -0,0 +1,18 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr ""
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""

View File

@@ -0,0 +1,20 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr "Productions"
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr "Productions"
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""
"Vous ne pouvez pas réinitialiser à l'état brouillon la production "
"« %(production)s » car elle a été générée par une vente."

View File

@@ -0,0 +1,18 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr ""
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""

View File

@@ -0,0 +1,18 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr ""
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""

View File

@@ -0,0 +1,20 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr "Produzioni"
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr "Produzioni"
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""
"Non puoi reimpostare la produzione \"%(production)s\" come bozza perché tale"
" è stata generata da una vendita."

View File

@@ -0,0 +1,18 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr ""
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""

View File

@@ -0,0 +1,18 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr ""
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""

View File

@@ -0,0 +1,20 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr "Producties"
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr "Producties"
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""
"U kunt de productie \"%(production)s\" niet terugzetten naar concept omdat "
"deze is gegenereerd door een verkoop."

View File

@@ -0,0 +1,18 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr ""
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""

View File

@@ -0,0 +1,18 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr ""
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""

View File

@@ -0,0 +1,20 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr "Producții"
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr "Producții"
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""
"\"%(production)s\" nu poate fi resetat la draft pentru că a fost generat de "
"o vânzare."

View File

@@ -0,0 +1,18 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr ""
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""

View File

@@ -0,0 +1,18 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr ""
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""

View File

@@ -0,0 +1,18 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr ""
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""

View File

@@ -0,0 +1,18 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr ""
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""

View File

@@ -0,0 +1,18 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr ""
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_production_reset_draft"
msgid ""
"You cannot reset production \"%(production)s\" to draft because it was "
"generated by a sale."
msgstr ""

View File

@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data grouped="1">
<record model="ir.message" id="msg_production_reset_draft">
<field name="text">You cannot reset production "%(production)s" to draft because it was generated by a sale.</field>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,18 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.pool import PoolMeta
from trytond.pyson import Eval
class Template(metaclass=PoolMeta):
__name__ = 'product.template'
@classmethod
def __setup__(cls):
super().__setup__()
cls.supply_on_sale.states['invisible'] &= (
~Eval('producible') | ~Eval('salable'))
class Product(metaclass=PoolMeta):
__name__ = 'product.product'

View File

@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="template_view_form">
<field name="model">product.template</field>
<field name="inherit" ref="product.template_view_form"/>
<field name="name">template_form</field>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,120 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from collections import defaultdict
from functools import wraps
from trytond.i18n import gettext
from trytond.model import Model, ModelView, Workflow
from trytond.model.exceptions import AccessError
from trytond.pool import Pool, PoolMeta
from trytond.tools import grouped_slice
from trytond.transaction import Transaction, without_check_access
def process_sale_supply(func):
@wraps(func)
def wrapper(cls, productions):
pool = Pool()
Sale = pool.get('sale.sale')
transaction = Transaction()
context = transaction.context
sales = set()
with without_check_access():
for sub_productions in grouped_slice(productions):
ids = [p.id for p in sub_productions]
sales.update([s.id for s in Sale.search([
('lines.productions', 'in', ids),
])])
result = func(cls, productions)
if sales:
with transaction.set_context(
queue_batch=context.get('queue_batch', True)):
Sale.__queue__.process(sales)
return result
return wrapper
class Production(metaclass=PoolMeta):
__name__ = 'production'
@classmethod
def _get_origin(cls):
return super()._get_origin() | {'sale.line'}
@classmethod
@process_sale_supply
def on_delete(cls, productions):
return super().on_delete(productions)
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
@process_sale_supply
def cancel(cls, productions):
super().cancel(productions)
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, productions):
pool = Pool()
SaleLine = pool.get('sale.line')
for production in productions:
if (production.state == 'cancelled'
and isinstance(production.origin, SaleLine)):
raise AccessError(
gettext('sale_supply_production'
'.msg_production_reset_draft',
production=production.rec_name))
super().draft(productions)
@classmethod
@ModelView.button
@Workflow.transition('running')
@process_sale_supply
def run(cls, productions):
super().run(productions)
@classmethod
@ModelView.button
@Workflow.transition('done')
@process_sale_supply
def do(cls, productions):
super().do(productions)
for production in productions:
production.assign_supplied()
def assign_supplied(self, grouping=('product',), filter_=None):
pool = Pool()
SaleLine = pool.get('sale.line')
if isinstance(self.origin, SaleLine):
sale_line = self.origin
else:
return
def filter_func(move):
if filter_ is None:
return True
for fieldname, values in filter_:
value = getattr(move, fieldname)
if isinstance(value, Model):
value = value.id
if value not in values:
return False
def get_key(move):
key = (move.to_location.id,)
for field in grouping:
value = getattr(move, field)
if isinstance(value, Model):
value = value.id
key += (value,)
return key
pbl = defaultdict(lambda: defaultdict(int))
for move in filter(filter_func, self.outputs):
pbl[move.product][get_key(move)] += move.internal_quantity
sale_line.assign_supplied(pbl[sale_line.product], grouping=grouping)

View File

@@ -0,0 +1,126 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime as dt
from trytond.model import fields
from trytond.pool import Pool, PoolMeta
from trytond.transaction import Transaction
class Sale(metaclass=PoolMeta):
__name__ = 'sale.sale'
@classmethod
def _process_supply(cls, sales, product_quantities):
pool = Pool()
Production = pool.get('production')
productions = []
for sale in sales:
productions.extend(sale.create_productions(product_quantities))
Production.save(productions)
Production.set_moves(productions)
super()._process_supply(sales, product_quantities)
def create_productions(self, product_quantities):
productions = []
for line in self.lines:
production = line.get_production(product_quantities)
if not production:
continue
production.set_planned_start_date()
productions.append(production)
assert not line.productions
return productions
class Line(metaclass=PoolMeta):
__name__ = 'sale.line'
productions = fields.One2Many(
'production', 'origin', "Productions", readonly=True)
@property
def has_supply(self):
return super().has_supply or bool(self.productions)
def get_supply_state(self, name):
state = super().get_supply_state(name)
if self.productions:
states = {p.state for p in self.productions}
if states <= {'running', 'done', 'cancelled'}:
if states == {'cancelled'}:
state = 'cancelled'
else:
state = 'supplied'
else:
state = 'requested'
return state
@classmethod
def copy(cls, lines, default=None):
if default is None:
default = {}
else:
default = default.copy()
default.setdefault('productions', None)
return super().copy(lines, default=default)
def get_production(self, product_quantities, bom_pattern=None):
"Return production for the sale line"
pool = Pool()
Production = pool.get('production')
Date = pool.get('ir.date')
Uom = pool.get('product.uom')
with Transaction().set_context(company=self.sale.company.id):
today = Date.today()
if (not self.supply_on_sale
or self.productions
or not self.ready_for_supply
or not self.product.producible):
return
product = self.product
quantity = self._get_move_quantity('out')
if product.supply_on_sale == 'stock_first':
available_qty = product_quantities[product]
available_qty = Uom.compute_qty(
product.default_uom, available_qty, self.unit,
round=False)
if quantity < available_qty:
product_quantities[product] -= Uom.compute_qty(
self.unit, quantity, product.default_uom, round=False)
return
date = self.shipping_date or today
if date <= today:
date = today
else:
date -= dt.timedelta(1)
pbom = product.get_bom(bom_pattern)
return Production(
planned_date=date,
company=self.sale.company,
warehouse=self.warehouse,
location=self.warehouse.production_location,
product=product,
bom=pbom.bom if pbom else None,
unit=self.unit,
quantity=quantity,
state='request',
origin=self,
)
class Line_Routing(metaclass=PoolMeta):
__name__ = 'sale.line'
def get_production(self, product_quantities, bom_pattern=None):
production = super().get_production(
product_quantities, bom_pattern=bom_pattern)
if production and production.product:
if pbom := production.product.get_bom(bom_pattern):
production.routing = pbom.routing
return production

View File

@@ -0,0 +1,20 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.action.act_window" id="act_production_form">
<field name="name">Productions</field>
<field name="res_model">production</field>
<field
name="domain"
eval="[If(Eval('active_ids', []) == [Eval('active_id')], ('origin.sale.id', '=', Eval('active_id'), 'sale.line'), ('origin.sale.id', 'in', Eval('active_ids'), 'sale.line'))]"
pyson="1"/>
</record>
<record model="ir.action.keyword" id="act_production_form_keyword1">
<field name="keyword">form_relate</field>
<field name="model">sale.sale,-1</field>
<field name="action" ref="act_production_form"/>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,2 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

View File

@@ -0,0 +1,152 @@
===============================
Sale Supply Production Scenario
===============================
Imports::
>>> from decimal import Decimal
>>> from proteus import Model
>>> from trytond.modules.account.tests.tools import create_chart, get_accounts
>>> from trytond.modules.company.tests.tools import create_company
>>> from trytond.tests.tools import activate_modules, assertEqual
Activate modules::
>>> config = activate_modules(
... 'sale_supply_production', create_company, create_chart)
Get accounts::
>>> accounts = get_accounts()
Create parties::
>>> Party = Model.get('party.party')
>>> customer = Party(name='Customer')
>>> customer.save()
Create account category::
>>> ProductCategory = Model.get('product.category')
>>> account_category = ProductCategory(name="Account Category")
>>> account_category.accounting = True
>>> account_category.account_revenue = accounts['revenue']
>>> account_category.save()
Create product::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
>>> template = ProductTemplate()
>>> template.name = "Product"
>>> template.default_uom = unit
>>> template.type = 'goods'
>>> template.producible = True
>>> template.salable = True
>>> template.supply_on_sale = 'always'
>>> template.list_price = Decimal(30)
>>> template.account_category = account_category
>>> product, = template.products
>>> product.cost_price = Decimal(20)
>>> template.save()
>>> product, = template.products
Create components::
>>> template = ProductTemplate()
>>> template.name = "Component 1"
>>> template.default_uom = unit
>>> template.type = 'goods'
>>> template.list_price = Decimal(5)
>>> component, = template.products
>>> component.cost_price = Decimal(1)
>>> template.save()
>>> component, = template.products
Create bill of material::
>>> BOM = Model.get('production.bom')
>>> bom = BOM(name="Product")
>>> input = bom.inputs.new()
>>> input.product = component
>>> input.quantity = 5
>>> output = bom.outputs.new()
>>> output.product = product
>>> output.quantity = 1
>>> bom.save()
>>> product_bom = product.boms.new()
>>> product_bom.bom = bom
>>> product.save()
Sale 10 products::
>>> Sale = Model.get('sale.sale')
>>> sale = Sale()
>>> sale.party = customer
>>> line = sale.lines.new()
>>> line.product = product
>>> line.quantity = 10
>>> sale.click('quote')
>>> sale.click('confirm')
>>> sale.state
'processing'
>>> shipment, = sale.shipments
>>> move, = shipment.outgoing_moves
>>> move.state
'staging'
>>> move, = shipment.inventory_moves
>>> move.state
'staging'
Check the production::
>>> Production = Model.get('production')
>>> production, = Production.find([])
>>> production.state
'request'
>>> assertEqual(production.origin, sale.lines[0])
>>> assertEqual(production.product, product)
>>> assertEqual(production.bom, bom)
>>> production.quantity
10.0
Delete the production, recreate one::
>>> production.delete()
>>> production, = Production.find([])
>>> production.quantity
10.0
Start the production::
>>> production.click('draft')
>>> production.click('wait')
>>> production.click('assign_force')
>>> production.click('run')
>>> production.state
'running'
>>> shipment.reload()
>>> move, = shipment.outgoing_moves
>>> move.state
'draft'
>>> move, = shipment.inventory_moves
>>> move.state
'draft'
Finish the production::
>>> production.click('do')
>>> shipment.reload()
>>> move, = shipment.outgoing_moves
>>> move.state
'draft'
>>> move, = shipment.inventory_moves
>>> move.state
'assigned'

View File

@@ -0,0 +1,82 @@
=======================================
Sale Supply Production Routing Scenario
=======================================
Imports::
>>> from decimal import Decimal
>>> from proteus import Model
>>> from trytond.modules.company.tests.tools import create_company
>>> from trytond.tests.tools import activate_modules, assertEqual
Activate modules::
>>> config = activate_modules(
... ['sale_supply_production', 'production_routing'],
... create_company)
>>> BoM = Model.get('production.bom')
>>> Party = Model.get('party.party')
>>> ProductTemplate = Model.get('product.template')
>>> ProductUom = Model.get('product.uom')
>>> Production = Model.get('production')
>>> Routing = Model.get('production.routing')
>>> Sale = Model.get('sale.sale')
Create parties::
>>> Party = Model.get('party.party')
>>> customer = Party(name="Customer")
>>> customer.save()
Create product::
>>> unit, = ProductUom.find([('name', '=', "Unit")])
>>> template = ProductTemplate()
>>> template.name = "Product"
>>> template.default_uom = unit
>>> template.type = 'goods'
>>> template.producible = True
>>> template.salable = True
>>> template.supply_on_sale = 'always'
>>> template.list_price = Decimal(30)
>>> product, = template.products
>>> product.cost_price = Decimal(20)
>>> template.save()
>>> product, = template.products
Create a Bill of Material with routing::
>>> bom = BoM(name="product")
>>> _ = bom.outputs.new(product=product, quantity=1)
>>> bom.save()
>>> routing = Routing(name="product")
>>> routing.boms.append(BoM(bom.id))
>>> routing.save()
>>> _ = product.boms.new(bom=bom, routing=routing)
>>> product.save()
Sale 10 products::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.invoice_method = 'manual'
>>> line = sale.lines.new()
>>> line.product = product
>>> line.quantity = 10
>>> sale.click('quote')
>>> sale.click('confirm')
>>> sale.state
'processing'
Check the production::
>>> production, = Production.find([])
>>> production.state
'request'
>>> assertEqual(production.product, product)
>>> assertEqual(production.bom, bom)
>>> assertEqual(production.routing, routing)

View File

@@ -0,0 +1,106 @@
===========================================
Sale Supply Production Stock First Scenario
===========================================
Imports::
>>> from decimal import Decimal
>>> from proteus import Model
>>> from trytond.modules.account.tests.tools import create_chart, get_accounts
>>> from trytond.modules.company.tests.tools import create_company
>>> from trytond.tests.tools import activate_modules
Activate modules::
>>> config = activate_modules(
... 'sale_supply_production', create_company, create_chart)
>>> Inventory = Model.get('stock.inventory')
>>> Location = Model.get('stock.location')
>>> Party = Model.get('party.party')
>>> ProductCategory = Model.get('product.category')
>>> ProductTemplate = Model.get('product.template')
>>> Sale = Model.get('sale.sale')
>>> UoM = Model.get('product.uom')
Get accounts::
>>> accounts = get_accounts()
Create parties::
>>> customer = Party(name='Customer')
>>> customer.save()
Create account category::
>>> account_category = ProductCategory(name="Account Category")
>>> account_category.accounting = True
>>> account_category.account_revenue = accounts['revenue']
>>> account_category.save()
Create product::
>>> unit, = UoM.find([('name', '=', 'Unit')])
>>> template = ProductTemplate()
>>> template.name = "Product"
>>> template.default_uom = unit
>>> template.type = 'goods'
>>> template.producible = True
>>> template.salable = True
>>> template.supply_on_sale = 'stock_first'
>>> template.list_price = Decimal(30)
>>> template.account_category = account_category
>>> product, = template.products
>>> product.cost_price = Decimal(20)
>>> template.save()
>>> product, = template.products
Fill warehouse::
>>> inventory = Inventory()
>>> inventory.location, = Location.find([('code', '=', 'STO')])
>>> line = inventory.lines.new()
>>> line.product = product
>>> line.quantity = 5
>>> inventory.click('confirm')
>>> inventory.state
'done'
Sale 3 products without production request::
>>> sale = Sale()
>>> sale.party = customer
>>> line = sale.lines.new()
>>> line.product = product
>>> line.quantity = 3
>>> sale.click('quote')
>>> sale.click('confirm')
>>> sale.state
'processing'
>>> line, = sale.lines
>>> len(line.productions)
0
>>> move, = line.moves
>>> move.state
'draft'
Sale 4 products with production request::
>>> sale = Sale()
>>> sale.party = customer
>>> line = sale.lines.new()
>>> line.product = product
>>> line.quantity = 4
>>> sale.click('quote')
>>> sale.click('confirm')
>>> sale.state
'processing'
>>> line, = sale.lines
>>> len(line.productions)
1
>>> move, = line.moves
>>> move.state
'staging'

View File

@@ -0,0 +1,13 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.tests.test_tryton import ModuleTestCase
class SaleSupplyProductionTestCase(ModuleTestCase):
'Test Sale Supply Production module'
module = 'sale_supply_production'
extras = ['production_routing']
del ModuleTestCase

View File

@@ -0,0 +1,8 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.tests.test_tryton import load_doc_tests
def load_tests(*args, **kwargs):
return load_doc_tests(__name__, __file__, *args, **kwargs)

View File

@@ -0,0 +1,24 @@
[tryton]
version=7.8.0
depends:
ir
production
sale_supply
extras_depend:
production_routing
xml:
sale.xml
product.xml
message.xml
[register]
model:
product.Template
product.Product
production.Production
sale.Sale
sale.Line
[register production_routing]
model:
sale.Line_Routing

View File

@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//page[@id='production']/field[@name='producible']" position="after">
<label name="supply_on_sale"/>
<field name="supply_on_sale"/>
<newline/>
</xpath>
</data>