first commit
This commit is contained in:
2
modules/sale_supply_production/__init__.py
Normal file
2
modules/sale_supply_production/__init__.py
Normal 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.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
modules/sale_supply_production/__pycache__/sale.cpython-311.pyc
Normal file
BIN
modules/sale_supply_production/__pycache__/sale.cpython-311.pyc
Normal file
Binary file not shown.
18
modules/sale_supply_production/locale/bg.po
Normal file
18
modules/sale_supply_production/locale/bg.po
Normal 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 ""
|
||||
20
modules/sale_supply_production/locale/ca.po
Normal file
20
modules/sale_supply_production/locale/ca.po
Normal 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."
|
||||
18
modules/sale_supply_production/locale/cs.po
Normal file
18
modules/sale_supply_production/locale/cs.po
Normal 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 ""
|
||||
20
modules/sale_supply_production/locale/de.po
Normal file
20
modules/sale_supply_production/locale/de.po
Normal 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."
|
||||
20
modules/sale_supply_production/locale/es.po
Normal file
20
modules/sale_supply_production/locale/es.po
Normal 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."
|
||||
18
modules/sale_supply_production/locale/es_419.po
Normal file
18
modules/sale_supply_production/locale/es_419.po
Normal 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 ""
|
||||
18
modules/sale_supply_production/locale/et.po
Normal file
18
modules/sale_supply_production/locale/et.po
Normal 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 ""
|
||||
18
modules/sale_supply_production/locale/fa.po
Normal file
18
modules/sale_supply_production/locale/fa.po
Normal 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 ""
|
||||
18
modules/sale_supply_production/locale/fi.po
Normal file
18
modules/sale_supply_production/locale/fi.po
Normal 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 ""
|
||||
20
modules/sale_supply_production/locale/fr.po
Normal file
20
modules/sale_supply_production/locale/fr.po
Normal 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."
|
||||
18
modules/sale_supply_production/locale/hu.po
Normal file
18
modules/sale_supply_production/locale/hu.po
Normal 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 ""
|
||||
18
modules/sale_supply_production/locale/id.po
Normal file
18
modules/sale_supply_production/locale/id.po
Normal 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 ""
|
||||
20
modules/sale_supply_production/locale/it.po
Normal file
20
modules/sale_supply_production/locale/it.po
Normal 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."
|
||||
18
modules/sale_supply_production/locale/lo.po
Normal file
18
modules/sale_supply_production/locale/lo.po
Normal 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 ""
|
||||
18
modules/sale_supply_production/locale/lt.po
Normal file
18
modules/sale_supply_production/locale/lt.po
Normal 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 ""
|
||||
20
modules/sale_supply_production/locale/nl.po
Normal file
20
modules/sale_supply_production/locale/nl.po
Normal 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."
|
||||
18
modules/sale_supply_production/locale/pl.po
Normal file
18
modules/sale_supply_production/locale/pl.po
Normal 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 ""
|
||||
18
modules/sale_supply_production/locale/pt.po
Normal file
18
modules/sale_supply_production/locale/pt.po
Normal 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 ""
|
||||
20
modules/sale_supply_production/locale/ro.po
Normal file
20
modules/sale_supply_production/locale/ro.po
Normal 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."
|
||||
18
modules/sale_supply_production/locale/ru.po
Normal file
18
modules/sale_supply_production/locale/ru.po
Normal 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 ""
|
||||
18
modules/sale_supply_production/locale/sl.po
Normal file
18
modules/sale_supply_production/locale/sl.po
Normal 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 ""
|
||||
18
modules/sale_supply_production/locale/tr.po
Normal file
18
modules/sale_supply_production/locale/tr.po
Normal 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 ""
|
||||
18
modules/sale_supply_production/locale/uk.po
Normal file
18
modules/sale_supply_production/locale/uk.po
Normal 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 ""
|
||||
18
modules/sale_supply_production/locale/zh_CN.po
Normal file
18
modules/sale_supply_production/locale/zh_CN.po
Normal 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 ""
|
||||
10
modules/sale_supply_production/message.xml
Normal file
10
modules/sale_supply_production/message.xml
Normal 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>
|
||||
18
modules/sale_supply_production/product.py
Normal file
18
modules/sale_supply_production/product.py
Normal 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'
|
||||
12
modules/sale_supply_production/product.xml
Normal file
12
modules/sale_supply_production/product.xml
Normal 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>
|
||||
120
modules/sale_supply_production/production.py
Normal file
120
modules/sale_supply_production/production.py
Normal 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)
|
||||
126
modules/sale_supply_production/sale.py
Normal file
126
modules/sale_supply_production/sale.py
Normal 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
|
||||
20
modules/sale_supply_production/sale.xml
Normal file
20
modules/sale_supply_production/sale.xml
Normal 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>
|
||||
2
modules/sale_supply_production/tests/__init__.py
Normal file
2
modules/sale_supply_production/tests/__init__.py
Normal 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.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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'
|
||||
@@ -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)
|
||||
@@ -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'
|
||||
13
modules/sale_supply_production/tests/test_module.py
Normal file
13
modules/sale_supply_production/tests/test_module.py
Normal 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
|
||||
8
modules/sale_supply_production/tests/test_scenario.py
Normal file
8
modules/sale_supply_production/tests/test_scenario.py
Normal 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)
|
||||
24
modules/sale_supply_production/tryton.cfg
Normal file
24
modules/sale_supply_production/tryton.cfg
Normal 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
|
||||
10
modules/sale_supply_production/view/template_form.xml
Normal file
10
modules/sale_supply_production/view/template_form.xml
Normal 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>
|
||||
Reference in New Issue
Block a user