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,12 @@
# 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.exceptions import UserError, UserWarning
class StockQuantityError(UserError):
pass
class StockQuantityWarning(UserWarning):
pass

View File

@@ -0,0 +1,21 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Sale Stock Quantity"

View File

@@ -0,0 +1,25 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
"La quantitat prevista %(forecast_quantity)s)s del producte\"%(product)s\" és"
" menor que la seva quantitat (%(quantity)s)."
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
"La quantitat prevista %(forecast_quantity)s de la línia \"%(line)s\" és "
"menor que la seva quantitat (%(quantity)s)."
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Força quantitat d'existències en venda"

View File

@@ -0,0 +1,21 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Sale Stock Quantity"

View File

@@ -0,0 +1,25 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
"Die erwartete Menge (%(forecast_quantity)s) des Artikels \"%(product)s\" ist"
" geringer als die Menge (%(quantity)s)."
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
"Die erwartete Menge (%(forecast_quantity)s) für die Warenbewegung "
"\"%(line)s\" ist geringer als die zu bewegende Menge (%(quantity)s)."
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Verkauf Lagerbestand"

View File

@@ -0,0 +1,25 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
"La cantidad prevista (%(forecast_quantity)s) del producto\"%(product)s\" es "
"menor que su cantidad (%(quantity)s)."
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
"La cantidad prevista (%(forecast_quantity)s) de la linea \"%(line)s\" es "
"menor que su cantidad (%(quantity)s)."
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Forzar cantidad de existencias en venta"

View File

@@ -0,0 +1,21 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr ""

View File

@@ -0,0 +1,25 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, fuzzy, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
"Prognoositav kogus (%(forecast_quantity)s%(default_uom)s) reale \"%(line)s\""
" on väiksem, kui rida (%(quantity)s%(unit)s)."
#, fuzzy, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
"Prognoositav kogus (%(forecast_quantity)s%(default_uom)s) reale \"%(line)s\""
" on väiksem, kui rida (%(quantity)s%(unit)s)."
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Vaba laoseis"

View File

@@ -0,0 +1,25 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, fuzzy, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
"مقدار پیش بینی: \"%s\" با واحداندازه گیری پیش فرض: \"%s\" از سطر: \"%s\" ، "
"کمتر از مقدار : \"%s\" با واحد : \"%s\" میباشد."
#, fuzzy, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
"مقدار پیش بینی: \"%s\" با واحداندازه گیری پیش فرض: \"%s\" از سطر: \"%s\" ، "
"کمتر از مقدار : \"%s\" با واحد : \"%s\" میباشد."
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "مقدار فروش موجودی"

View File

@@ -0,0 +1,21 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Sale Stock Quantity"

View File

@@ -0,0 +1,25 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
"La quantité prévisionnelle (%(forecast_quantity)s) du produit "
"« %(product)s » est plus petite que la quantité (%(quantity)s)."
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
"La quantité prévisionnelle (%(forecast_quantity)s) de la ligne « %(line)s » "
"est plus petite que sa quantité (%(quantity)s)."
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Quantité de stock de vente"

View File

@@ -0,0 +1,21 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Sale Stock Quantity"

View File

@@ -0,0 +1,25 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, fuzzy, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
"Jumlah perkiraan (%(forecast_quantity)s%(default_uom)s) pada baris "
"\"%(line)s\" lebih rendah dari jumlah (%(quantity)s%(unit)s)."
#, fuzzy, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
"Jumlah perkiraan (%(forecast_quantity)s%(default_uom)s) pada baris "
"\"%(line)s\" lebih rendah dari jumlah (%(quantity)s%(unit)s)."
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr ""

View File

@@ -0,0 +1,25 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
"La quantita prevista (%(forecast_quantity)s) del prodotto \"%(product)s\" è "
"inferiore alla quantità (%(quantity)s)."
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
"La quantita prevista (%(forecast_quantity)s) per la riga \"%(line)s\" è "
"inferiore alla quantità (%(quantity)s)."
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Quantità stock in vendita"

View File

@@ -0,0 +1,26 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, fuzzy, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
"ການຄາດຄະເນຈຳນວນ %(forecast_quantity)s%(default_uom)s ຂອງ \"%(line)s\" "
"ຕໍ່າກວ່າ %(quantity)s%(unit)s."
#, fuzzy, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
"ການຄາດຄະເນຈຳນວນ %(forecast_quantity)s%(default_uom)s ຂອງ \"%(line)s\" "
"ຕໍ່າກວ່າ %(quantity)s%(unit)s."
#, fuzzy
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Sale Stock Quantity"

View File

@@ -0,0 +1,21 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Sale Stock Quantity"

View File

@@ -0,0 +1,25 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
"De verwachte hoeveelheid (%(forecast_quantity)s) van product \"%(product)s\""
" is lager dan de hoeveelheid (%(quantity)s)."
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
"De verwachte hoeveelheid (%(forecast_quantity)s) voor regel \"%(line)s\" is "
"lager dan de hoeveelheid (%(quantity)s) van de regel zelf."
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Verkoop Voorraad Aantal"

View File

@@ -0,0 +1,21 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Sale Stock Quantity"

View File

@@ -0,0 +1,25 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
"A quantidade prevista %(forecast_quantity)s do produto \"%(product)s\" é "
"menor do que (%(quantity)s)."
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
"A quantidade prevista (%(forecast_quantity)s) para a linha \"%(line)s\" é "
"menor do que (%(quantity)s)."
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Quantidade em Estoque"

View File

@@ -0,0 +1,25 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
"Cantitatea estimata (%(forecast_quantity)s) din produsul \"%(product)s\" "
"este mai mică decât cantitatea (%(quantity)s)."
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
"Cantitatea estimata (%(forecast_quantity)s) pentru rândul \"%(line)s\" este "
"mai mică decât cantitatea proprie (%(quantity)s)."
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Vânzare Cantitate Stoc"

View File

@@ -0,0 +1,21 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Sale Stock Quantity"

View File

@@ -0,0 +1,26 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, fuzzy, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
"Predvidena količina %(forecast_quantity)s%(default_uom)s postavke "
"\"%(line)s\" je manjša kot %(quantity)s%(unit)s."
#, fuzzy, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
"Predvidena količina %(forecast_quantity)s%(default_uom)s postavke "
"\"%(line)s\" je manjša kot %(quantity)s%(unit)s."
#, fuzzy
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Sale Stock Quantity"

View File

@@ -0,0 +1,21 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Sale Stock Quantity"

View File

@@ -0,0 +1,21 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr ""

View File

@@ -0,0 +1,21 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#, python-format
msgctxt "model:ir.message,text:msg_product_forecast_quantity_lower"
msgid ""
"The forecast quantity (%(forecast_quantity)s) of product \"%(product)s\" is "
"lower than the quantity (%(quantity)s)."
msgstr ""
#, python-format
msgctxt "model:ir.message,text:msg_sale_stock_quantity"
msgid ""
"The forcast quantity (%(forecast_quantity)s) for line \"%(line)s\" is lower "
"than its quantity (%(quantity)s)."
msgstr ""
msgctxt "model:res.group,name:group_sale_stock_quantity"
msgid "Sale Stock Quantity"
msgstr "Sale Stock Quantity"

View File

@@ -0,0 +1,13 @@
<?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_sale_stock_quantity">
<field name="text">The forcast quantity (%(forecast_quantity)s) for line "%(line)s" is lower than its quantity (%(quantity)s).</field>
</record>
<record model="ir.message" id="msg_product_forecast_quantity_lower">
<field name="text">The forecast quantity (%(forecast_quantity)s) of product "%(product)s" is lower than the quantity (%(quantity)s).</field>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,262 @@
# 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
from collections import defaultdict
from itertools import groupby
from operator import attrgetter
from trytond.i18n import gettext
from trytond.model import ModelView, Workflow, fields
from trytond.pool import Pool, PoolMeta
from trytond.tools import grouped_slice, sortable_values
from trytond.transaction import Transaction
from .exceptions import StockQuantityError, StockQuantityWarning
class Sale(metaclass=PoolMeta):
__name__ = 'sale.sale'
@classmethod
@ModelView.button
@Workflow.transition('quotation')
def quote(cls, sales):
# Check before setting the number
cls._check_stock_quantity(sales)
super().quote(sales)
@classmethod
@ModelView.button
@Workflow.transition('confirmed')
def confirm(cls, sales):
# Check before queueing the process task
cls._check_stock_quantity(sales)
super().confirm(sales)
@classmethod
def _check_stock_quantity(cls, sales):
for sale in sales:
sale.check_stock_quantity()
@classmethod
def _stock_quantity_states(cls):
return ['quotation', 'confirmed']
@classmethod
def _stock_quantity_next_supply_date(cls, product):
pool = Pool()
try:
PurchaseRequest = pool.get('purchase.request')
except KeyError:
PurchaseRequest = None
if (getattr(PurchaseRequest, 'get_supply_dates', None)
and product.purchasable):
return PurchaseRequest.get_supply_dates(product)[0]
else:
# TODO compute supply date for production
return datetime.date.max
def check_stock_quantity(self):
pool = Pool()
Product = pool.get('product.product')
Date = pool.get('ir.date')
Uom = pool.get('product.uom')
Group = pool.get('res.group')
User = pool.get('res.user')
ModelData = pool.get('ir.model.data')
Lang = pool.get('ir.lang')
Line = pool.get('sale.line')
Warning = pool.get('res.user.warning')
if not self.warehouse:
return
transaction = Transaction()
def in_group():
group = Group(ModelData.get_id('sale_stock_quantity',
'group_sale_stock_quantity'))
user_id = transaction.user
if user_id == 0:
user_id = transaction.context.get('user', user_id)
if user_id == 0:
return True
user = User(user_id)
return group in user.groups
def filter_line(line):
return (line.warehouse
and line.product
and line.product.type == 'goods'
and not line.product.consumable
and line.quantity > 0
# Use getattr as supply_on_sale comes from sale_supply module
and not getattr(line, 'supply_on_sale', False))
def get_delta(date, warehouse, products):
'Compute quantity delta at the date'
if date in date2delta:
return date2delta[warehouse][date]
with transaction.set_context(forecast=True,
stock_date_start=date,
stock_date_end=date):
pbl = defaultdict(int)
for sub_products in grouped_slice(products):
sub_product_ids = [p.id for p in sub_products]
pbl.update(Product.products_by_location(
[warehouse.id],
with_childs=True,
grouping_filter=(sub_product_ids,)))
delta = {}
for key, qty in pbl.items():
_, product_id = key
delta[product_id] = qty
date2delta[warehouse][date] = delta
return delta
date2delta = defaultdict(dict)
def raise_(line_id, message_values):
if not in_group():
raise StockQuantityError(
gettext('sale_stock_quantity.msg_sale_stock_quantity',
**message_values))
warning_name = 'stock_quantity_warning_%s' % line_id
if Warning.check(warning_name):
raise StockQuantityWarning(warning_name,
gettext('sale_stock_quantity.msg_sale_stock_quantity',
**message_values))
with Transaction().set_context(company=self.company.id):
today = Date.today()
lang = Lang.get()
lines = filter(filter_line, self.lines)
lines = list(lines)
quantities = defaultdict(lambda: defaultdict(int))
w_getter = attrgetter('warehouse', 'shipping_date')
w_products = {}
for (warehouse, shipping_date), w_lines in groupby(
sorted(lines, key=sortable_values(w_getter)), key=w_getter):
if shipping_date is None:
shipping_date = today
w_products[warehouse] = products = {l.product for l in w_lines}
with transaction.set_context(
locations=[warehouse.id],
stock_date_end=shipping_date,
stock_assign=True):
products = Product.browse(products)
quantities[warehouse].update(
(p, p.forecast_quantity) for p in products)
# Remove quantities from other sales
for sub_products in grouped_slice(products):
other_lines = Line.search([
('sale.company', '=', self.company.id),
('sale.state', 'in', self._stock_quantity_states()),
('sale.id', '!=', self.id),
('product', 'in', sub_products),
('quantity', '>', 0),
])
for line in other_lines:
if line.warehouse != warehouse:
continue
if (line.shipping_date
and line.shipping_date > shipping_date):
continue
product = line.product
date = line.sale.sale_date or today
if date > today:
continue
quantity = Uom.compute_qty(line.unit, line.quantity,
product.default_uom, round=False)
quantities[line.warehouse][product] -= quantity
for line in lines:
warehouse = line.warehouse
product = line.product
quantity = Uom.compute_qty(line.unit, line.quantity,
product.default_uom, round=False)
shipping_date = line.shipping_date or today
next_supply_date = self._stock_quantity_next_supply_date(product)
message_values = {
'line': line.rec_name,
'forecast_quantity': lang.format_number_symbol(
quantities[warehouse][product],
product.default_uom, product.default_uom.digits),
'quantity': lang.format_number_symbol(
line.quantity, line.unit, line.unit.digits),
}
if (quantities[warehouse][product] < quantity
and shipping_date < next_supply_date):
raise_(line.id, message_values)
# Update quantities if the same product is many times in lines
quantities[warehouse][product] -= quantity
# Check other dates until next supply date
if next_supply_date != datetime.date.max:
products = w_products[warehouse]
forecast_quantity = quantities[warehouse][product]
date = shipping_date + datetime.timedelta(1)
while date < next_supply_date:
delta = get_delta(date, warehouse, products)
forecast_quantity += delta.get(product.id, 0)
if forecast_quantity < 0:
message_values['forecast_quantity'] = forecast_quantity
raise_(line.id, message_values)
date += datetime.timedelta(1)
class Line(metaclass=PoolMeta):
__name__ = 'sale.line'
@fields.depends(methods=['_notify_stock_quantity'])
def on_change_notify(self):
notifications = super().on_change_notify()
notifications.extend(self._notify_stock_quantity())
return notifications
@fields.depends(
'sale_state', 'product', 'quantity', 'unit', 'company',
'shipping_date', 'sale', '_parent_sale.warehouse')
def _notify_stock_quantity(self):
pool = Pool()
Date = pool.get('ir.date')
Lang = pool.get('ir.lang')
Move = pool.get('stock.move')
Product = pool.get('product.product')
UoM = pool.get('product.uom')
lang = Lang.get()
if (self.sale_state == 'draft'
and self.sale
and self.sale.warehouse
and self.product
and self.product.type in Move.get_product_types()
and self.unit
and self.quantity is not None):
with Transaction().set_context(
company=self.company.id if self.company else None):
today = Date.today()
shipping_date = self.shipping_date or today
locations = [self.sale.warehouse.id]
with Transaction().set_context(
locations=locations,
stock_date_end=shipping_date):
product = Product(self.product.id)
quantity = UoM.compute_qty(
self.unit, self.quantity,
product.default_uom, round=False)
if product.forecast_quantity < quantity:
yield ('warning', gettext(
'sale_stock_quantity'
'.msg_product_forecast_quantity_lower',
forecast_quantity=lang.format_number_symbol(
product.forecast_quantity, product.default_uom,
product.default_uom.digits),
product=self.product.rec_name,
quantity=lang.format_number_symbol(
self.quantity, self.unit,
self.unit.digits)))

View File

@@ -0,0 +1,15 @@
<?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="res.group" id="group_sale_stock_quantity">
<field name="name">Sale Stock Quantity</field>
</record>
<record model="res.user-res.group"
id="user_admin_sale_stock_quantity">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_sale_stock_quantity"/>
</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,205 @@
===================
Sale Stock Quantity
===================
Imports::
>>> import datetime as dt
>>> from decimal import Decimal
>>> from proteus import Model
>>> from trytond.modules.account.tests.tools import (
... create_chart, create_fiscalyear, get_accounts)
>>> from trytond.modules.account_invoice.tests.tools import (
... create_payment_term, set_fiscalyear_invoice_sequences)
>>> from trytond.modules.company.tests.tools import create_company
>>> from trytond.modules.stock.exceptions import MoveFutureWarning
>>> from trytond.tests.tools import activate_modules
>>> today = dt.date.today()
>>> later = today + dt.timedelta(days=2)
Activate modules::
>>> config = activate_modules(
... ['sale_stock_quantity', 'stock_supply'],
... create_company, create_chart)
>>> Warning = Model.get('res.user.warning')
Create fiscal year::
>>> fiscalyear = set_fiscalyear_invoice_sequences(
... create_fiscalyear(today=(today, later)))
>>> fiscalyear.click('create_period')
Get accounts::
>>> accounts = get_accounts()
>>> revenue = accounts['revenue']
>>> expense = accounts['expense']
Create parties::
>>> Party = Model.get('party.party')
>>> customer = Party(name='Customer')
>>> customer.save()
>>> supplier = Party(name='Supplier')
>>> supplier.save()
Create account category::
>>> ProductCategory = Model.get('product.category')
>>> account_category = ProductCategory(name="Account Category")
>>> account_category.accounting = True
>>> account_category.account_expense = expense
>>> account_category.account_revenue = revenue
>>> account_category.save()
Create product::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> ProductTemplate = Model.get('product.template')
>>> template = ProductTemplate()
>>> template.name = 'product'
>>> template.default_uom = unit
>>> template.type = 'goods'
>>> template.purchasable = True
>>> template.salable = True
>>> template.list_price = Decimal('10')
>>> template.account_category = account_category
>>> template.save()
>>> product, = template.products
>>> ProductSupplier = Model.get('purchase.product_supplier')
>>> product_supplier = ProductSupplier()
>>> product_supplier.template = template
>>> product_supplier.party = supplier
>>> product_supplier.lead_time = dt.timedelta(3)
>>> product_supplier.save()
Create payment term::
>>> payment_term = create_payment_term()
>>> payment_term.save()
Create an Inventory of 5 products::
>>> Inventory = Model.get('stock.inventory')
>>> Location = Model.get('stock.location')
>>> storage, = Location.find([
... ('code', '=', 'STO'),
... ])
>>> inventory = Inventory()
>>> inventory.location = storage
>>> inventory_line = inventory.lines.new(product=product)
>>> inventory_line.quantity = 5.0
>>> inventory_line.expected_quantity = 0.0
>>> inventory.click('confirm')
>>> inventory.state
'done'
Sale 3 products with enough stock::
>>> Sale = Model.get('sale.sale')
>>> sale = Sale()
>>> sale.party = customer
>>> sale.payment_term = payment_term
>>> sale_line = sale.lines.new()
>>> sale_line.product = product
>>> sale_line.quantity = 3.0
>>> len(sale_line.notifications())
0
>>> sale.click('quote')
Sale 1 product with still enough stock::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.payment_term = payment_term
>>> sale_line = sale.lines.new()
>>> sale_line.product = product
>>> sale_line.quantity = 1.0
>>> len(sale_line.notifications())
0
>>> sale.click('quote')
Sale 2 more products with not enough stock::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.payment_term = payment_term
>>> sale_line = sale.lines.new()
>>> sale_line.product = product
>>> sale_line.quantity = 2.0
>>> len(sale_line.notifications())
0
>>> sale.click('quote')
Traceback (most recent call last):
...
StockQuantityWarning: ...
Clean sales::
>>> Sale.click(Sale.find([]), 'draft')
>>> Sale.delete(Sale.find([]))
Sale 6 products with not enough stock::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.payment_term = payment_term
>>> sale_line = sale.lines.new()
>>> sale_line.product = product
>>> sale_line.quantity = 6.0
>>> len(sale_line.notifications())
1
>>> sale.click('quote')
Traceback (most recent call last):
...
StockQuantityWarning: ...
>>> sale.delete()
Make an inventory of 3 products in 2 days::
>>> inventory = Inventory()
>>> inventory.location = storage
>>> inventory.date = later
>>> inventory_line = inventory.lines.new(product=product)
>>> inventory_line.quantity = 3.0
>>> inventory_line.expected_quantity = 5.0
>>> try:
... inventory.click('confirm')
... except MoveFutureWarning as warning:
... Warning(user=config.user, name=warning.name).save()
>>> inventory.click('confirm')
>>> inventory.state
'done'
Sale 4 products with not enough forecast::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.payment_term = payment_term
>>> sale_line = sale.lines.new()
>>> sale_line.product = product
>>> sale_line.quantity = 4.0
>>> sale.click('quote')
Traceback (most recent call last):
...
StockQuantityWarning: ...
>>> sale.delete()
Sale 2 products with enough forecast::
>>> sale = Sale()
>>> sale.party = customer
>>> sale.payment_term = payment_term
>>> sale_line = sale.lines.new()
>>> sale_line.product = product
>>> sale_line.quantity = 2.0
>>> sale.click('quote')
>>> sale.click('draft')
>>> sale.delete()

View File

@@ -0,0 +1,12 @@
# 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 SaleStockQuantityTestCase(ModuleTestCase):
'Test Sale Stock Quantity module'
module = 'sale_stock_quantity'
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,17 @@
[tryton]
version=7.8.0
depends:
product
sale
stock
extras_depend:
sale_supply
stock_supply
xml:
sale.xml
message.xml
[register]
model:
sale.Sale
sale.Line