Compare commits

..

24 commits

Author SHA1 Message Date
6c1eb903f7 Upgraded sqlservice to 2.0.x 2023-04-13 10:55:42 +02:00
229ea6fb5b Assed some modified Jeeves procedures, for historical reasons. 2023-02-02 14:21:21 +01:00
efc505c12e Warehouse module 2021-12-30 12:41:55 +01:00
5fe140714e Deps updates, handle more tables + LK code 2021-12-30 12:40:50 +01:00
f649b5f953 Make sure alt unit is an order unit when using amount per piece 2019-12-09 10:36:06 +01:00
1a7ada9d56 Fix: Verify string lengths when creating model objects 2019-11-20 00:04:37 +01:00
144fdbefb1 Handle exceptions in stored procedures 2019-11-19 16:24:41 +01:00
5d1b5f90ac Verify string lengths when creating model objects 2019-11-19 16:24:10 +01:00
f9686c5306 Fix NoneType error in Order __dict_args__ 2019-11-19 16:23:07 +01:00
2fa1e4aa7a Fix showing multiple web users for a single contact person 2019-10-23 15:28:37 +02:00
7310954468 Return VAT Rate for articles 2019-10-23 15:27:44 +02:00
1ae563654c Expose the default article class of commodity groups 2019-09-30 14:03:16 +02:00
7288e88135 Fix input of RequestedDeliveryDate in order head. Error handling in Company.get 2019-09-30 10:27:53 +02:00
0330e7a4bf Commit Jeeves modifications for future tracking 2019-09-23 14:14:23 +02:00
6339e9d1ce Fixes to pricing and customer models. Add req deliv to order. And more..
* Fix NullPriceAllowed and logic in price calculation
* Fix CustomerCategory join in Customer model
* Add RequestedDeliveryDate to order creation
* Start using Jeeves Signatures on order creation
2019-09-23 14:12:34 +02:00
fc7c1e13bc Implement support for contact persons and web users. Also show addr info for company. 2019-09-10 11:19:35 +02:00
a23e88ff2f Added 'WholeSaleAmount' to Article, and refactored article internal functions 2019-09-06 11:40:53 +02:00
cf7ed09049 Fix get conversion factor NoneType error, for articles. 2019-09-06 08:25:59 +02:00
ee902ee733 Set WholeSaleUnit to altUnit if Extra1 is not set. Fix webUserName. 2019-09-05 15:26:18 +02:00
0af38e286e Stored procedure helpers. Order repo and model. More SQLService updates.
* A generic stored procedure helper added, and support for calling them.
* Order and OrderItem tables added, including helpers and calls to SP for creation and updates.
* Minor updates to other repositories.
2019-08-30 12:09:10 +02:00
b77a7069ce Started using 'sqlservice'. Added support for many new tables. 2019-07-04 14:25:42 +02:00
9b7d7db996 In progress: Fix configuration logic 2019-04-08 11:02:14 +02:00
0fae8725e0 Delete config.yml 2019-04-05 14:51:49 +00:00
28726fee01 In progress: Jeeves communication is now based on SQLAlchemy 2019-04-05 16:50:38 +02:00
35 changed files with 11867 additions and 136 deletions

View file

@ -1,3 +1,6 @@
# Jeeves specific scripts
jeeves_sp_updates/
# Git # Git
.git .git
.gitignore .gitignore

6
.gitignore vendored
View file

@ -1,3 +1,5 @@
*.csv
*.py[cod] *.py[cod]
# C extensions # C extensions
@ -65,4 +67,6 @@ docs/_build
*.bak *.bak
*.log *.log
*.xls *.xls
config.yml

View file

@ -4,6 +4,7 @@ LABEL description="PyJeeves syncronization application" \
maintainer="Marcus Lindvall <marcus.lindvall@gmail.com>" maintainer="Marcus Lindvall <marcus.lindvall@gmail.com>"
RUN apk add --no-cache build-base freetds-dev git \ RUN apk add --no-cache build-base freetds-dev git \
&& pip install --no-cache-dir cython \
&& pip install --no-cache-dir git+https://github.com/pymssql/pymssql.git \ && pip install --no-cache-dir git+https://github.com/pymssql/pymssql.git \
&& apk del --purge build-base freetds-dev git && apk del --purge build-base freetds-dev git
@ -12,7 +13,9 @@ RUN apk add --no-cache freetds
WORKDIR /app WORKDIR /app
COPY ./requirements.txt /app/requirements.txt COPY ./requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r requirements.txt RUN apk add --no-cache build-base libffi-dev openssl-dev \
&& pip install --no-cache-dir -r requirements.txt \
&& apk del --purge build-base
COPY . /app COPY . /app

22
README.md Normal file
View file

@ -0,0 +1,22 @@
# PyJeeves Module
This project is a Jeeves data extraction and integration project.
## Initial creation of database schema.
```bash
docker run --link db --network marcus_default -v /srv/pyjeeves/config.yml:/app/config.yml gitlab.lndvll.se:5500/lindvallskaffe/pyjeeves python ./pyjeeves/db_raw.py
```
## Connecting to DB with client
```bash
docker run -it --network marcus_default --link db:mysql --rm mysql sh -c 'exec mysql -h"db" -P"3306" -uroot -p"ROOT_PW"'
```
## Forcing updates
You may force updates of objects by setting RowUpdatedDt to null.
For example:
´update jvs_customers set RowUpdatedDt = null;´

View file

@ -1,22 +0,0 @@
PyJeeves Module
===============
This project is a Jeeves data extraction and integration project.
## Initial creation of database schema.
´docker run --link db --network marcus_default -v /srv/pyjeeves/config.yml:/app/config.yml gitlab.lndvll.se:5500/lindvallskaffe/pyjeeves python ./pyjeeves/db.py´
## Connecting to DB with client
´docker run -it --network marcus_default --link db:mysql --rm mysql sh -c 'exec mysql -h"db" -P"3306" -uroot -p"ROOT_PW"'´
## Forcing updates
You may force updates of objects by setting RowUpdatedDt to null.
For example:
´update jvs_customers set RowUpdatedDt = null;´

View file

@ -1,50 +0,0 @@
sync_interval: 60
mysql:
host: localhost
port: 3306
user: pyjeeves
passwd: jeeves
db: pyjeeves
jeeves_db:
server: 'BlackSheep01'
database: 'LKTest'
user: 'jvsdbo'
password: 'password'
logging:
version: 1
handlers:
fileHandler:
class: logging.FileHandler
formatter: simpleFormatter
filename: pyjeeves.log
level: INFO
consoleHandler:
class: logging.StreamHandler
level: DEBUG
formatter: simpleFormatter
stream: ext://sys.stdout
loggers:
PyJeeves:
handlers:
- fileHandler
- consoleHandler
level: DEBUG
alembic:
handlers:
- consoleHandler
level: INFO
sqlalchemy:
handlers:
- consoleHandler
level: WARN
qualname: sqlalchemy.engine
formatters:
simpleFormatter:
format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
alembic:
script_location: migrations
sqlalchemy.url: 'mysql+pymysql://pyjeeves:jeeves@localhost/pyjeeves?charset=utf8mb4'

View file

@ -3,12 +3,14 @@ version: '2'
services: services:
db: db:
image: mysql image: mysql
restart: always restart: unless-stopped
environment: environment:
MYSQL_ROOT_PASSWORD: rootpassword MYSQL_ROOT_PASSWORD: J33v3s33
MYSQL_USER: pyjeeves MYSQL_USER: pyjeeves
MYSQL_PASSWORD: jeeves MYSQL_PASSWORD: jeeves
MYSQL_DATABASE: pyjeeves MYSQL_DATABASE: pyjeeves
ports:
- 3306:3306
pyjeeves: pyjeeves:
container_name: pyjeeves container_name: pyjeeves
@ -49,4 +51,4 @@ services:
environment: environment:
- POSTGRES_DB=metabase - POSTGRES_DB=metabase
- POSTGRES_USER=metabase - POSTGRES_USER=metabase
- POSTGRES_PASSWORD="m3t@b@s3" - POSTGRES_PASSWORD="m3t@b@s3"

View file

@ -0,0 +1,852 @@
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO
SET NOCOUNT ON
GO
PRINT 'Begin Jeeves_Esales_AddOrderRow'
GO
PRINT 'Begin Jeeves_Esales_AddOrderRow'
PRINT '***************************************************'
PRINT '* *'
PRINT '* Jeeves_Esales_AddOrderRow *'
PRINT '* *'
PRINT '***************************************************'
GO
IF OBJECT_ID( 'Jeeves_Esales_AddOrderRow', 'P' ) IS NOT NULL
BEGIN
DROP PROCEDURE Jeeves_Esales_AddOrderRow
END
GO
IF (SELECT COUNT(*) FROM xspr WHERE
SprTrgName = 'Jeeves_Esales_AddOrderRow' AND
SprTrgType = 'P' ) > 0
BEGIN
UPDATE xspr SET beskrivning=beskrivning WHERE
SprTrgName = 'Jeeves_Esales_AddOrderRow' AND
SprTrgType = 'P'
END ELSE BEGIN
INSERT INTO xspr(SprTrgName,SprTrgType,RowCreatedDt)
VALUES('Jeeves_Esales_AddOrderRow' , 'P', GETDATE() )
END
GO
----------------------------------------------------
CREATE procedure Jeeves_Esales_AddOrderRow
@c_ForetagKod smallint,
@c_OrderNumber int,
@c_webUserName Jeeves_StrVarChar64 = null,
@c_CompanyNo Jeeves_StrVarChar32 = null,
@c_PersSign Jeeves_StrVarChar6,
@c_LangID smallint = null,
@c_ItemNo Jeeves_StrVarChar32 = null,
@c_Qty Jeeves_Qty = 0,
@c_QtyAltEnh Jeeves_Qty = 0,
@c_RequestedDate Jeeves_StrVarChar10 = null,
@c_BatchId Jeeves_StrVarChar16 = null,
@c_ArtSerieNr Jeeves_StrVarChar32 = null,
@c_OrderType smallint = null,
@c_Run_Type char(1) = null,
@c_TemplateRowID int = null,
@c_Edit Jeeves_StrVarCharMax = null,
@c_EditExt Jeeves_StrVarCharMax = null,
@c_LagStalle Jeeves_StrVarChar10 = null,
@c_AltEnhetKod Jeeves_StrVarChar10 = null,
@c_AllocateAvailable smallint = 0,
@c_OverrideCreditLimit char(1) = 0,
@o_OrderRow int = null output,
@o_NextQty Jeeves_Qty = null output,
@o_NextDate DateTime = null output,
@o_LastQty Jeeves_Qty = null output,
@o_LastDate DateTime = null output,
@o_AllocatedQty Jeeves_Qty = null output,
@o_AllocatedDate DateTime = null output
as
set nocount on
--set ansi_warnings o f f
--set concat_null_yields_null o f f
--set ansi_nulls o f f
--set ansi_padding o f f
declare @withPriceCalc char(1)
select @withPriceCalc = 'Y'
declare @oh_OrdStat smallint,
@orp_OrdRadSt smallint,
@orp_OrdRadNr int,
@orp_vbordradsum money,
@orp_vb_prisinklmoms Jeeves_Amount,
@orp_Vb_Pris Jeeves_Amount,
@wr Jeeves_StrVarChar256,
@x int,
@dbe int,
@dbc int,
@dbp int,
@Enter_TranCount as int,
@NewLine as nvarchar(2),
@PgmId Jeeves_StrVarChar256,
@salj_saljare Jeeves_StrVarChar32,
@kus_kundrabatt float,
@kus_rabklass smallint,
@kus_Kreditsparr char(1),
@kus_kundsaldo money,
@kus_ordsum money,
@kus_kundkredlim money,
@kus_momskod smallint,
@kus_offnr Jeeves_StrVarChar10,
@kus_KundRabattKod0 char(1),
@kus_KundRabattKod1 char(1),
@kus_KundRabattKod2 char(1),
@kus_PrisListaKundSpec int,
@kus_KundKategoriKod smallint,
@kus_tradecalcmarkup float,
@kus_tradefsgmarkup float,
@kus_AddArtEjAktiv char(1),
@ar_ArtProdKlass Jeeves_StrVarChar4,
@ar_OrdvRabKod smallint,
@ar_artkundrabkod char(1),
@ar_artrabklass smallint,
@ar_momskod smallint,
@ar_ordTyp smallint,
@sy1_PrisListaInklMoms char(1),
@sy1_momskod smallint,
@oh_vbordsum money,
@oh_ordsum money,
@oh_lagstalle Jeeves_StrVarChar10,
@Today datetime,
@Momssats float,
@CompanyNo Jeeves_StrVarChar20,
@ContactNo int,
@prislista int,
@Saljare Jeeves_StrVarChar32,
@ar_artfsgforp float,
@ar_palaggdelforp float,
@ar_palaggdelforpbelopp char(1),
@ar_lagstalle Jeeves_StrVarChar10,
@sy1_lagstalle Jeeves_StrVarChar10,
@ValKurs ztCurrencyRate,
@valkod Jeeves_StrVarChar4,
@Samfaktutskr char(1),
@Allowpurchase char(1),
@PCode int,
@sy1_artbeskr_2_orp char(1),
@ArtBeskr Jeeves_StrVarChar64,
@ArtBeskr2 Jeeves_StrVarChar256,
@LangID smallint,
@DateAvailable DateTime,
@sy1_rbn char(1),
@sy1_PrisListaStaffling int,
@sy1_KodAlternativEnhet int,
@AltEnhetKod Jeeves_StrVarChar10,
@AltEnhetOmrFaktor ZtPurQty2Stock,
@InkAvt int,
@LevNr JEEVES_CompanyNo,
@ArtLevPrior smallint,
@ArtHuvudAvt JEEVES_Boolean,
@SALES170 JEEVES_Boolean,
@SALES190 smallint,
@SALES048 smallint,
@SALES007 smallint,
@KostBar Jeeves_StrVarChar8,
@KostStalleKod Jeeves_StrVarChar8,
@K4 Jeeves_StrVarChar8,
@K5 Jeeves_StrVarChar8,
@K6 Jeeves_StrVarChar8,
@K7 Jeeves_StrVarChar8,
@AutoRegel Jeeves_StrVarChar4,
@AnskaffningTillv smallint
select @LangID = @c_LangID
exec Jeeves_Esales_GetUserInfo
@c_IntrnCoNo = @c_ForetagKod,
@c_webUserName = @c_webUserName,
@c_CompanyNo = @c_CompanyNo,
@o_LangID = @LangID output,
@o_CompanyNo = @CompanyNo output,
@o_ContactNo = @ContactNo output,
@o_Prislista = @Prislista output,
@o_Saljare = @Saljare output,
@o_Valkod = @Valkod output,
@o_ValKurs = @Valkurs output
--Adding from a template
if (isnull(@c_TemplateRowID,-1) > -1) begin
if exists(select 1 from web_orp where ConnToSQLIDENTITY = @c_TemplateRowID) begin
declare cr_items cursor static forward_only local for
select artnr, ordantal from web_orp where ConnToSQLIDENTITY = @c_TemplateRowID
open cr_items
while 1=1 begin
fetch next from cr_items into
@c_ItemNo,
@c_Qty
if @@Fetch_Status<>0 BREAK
if @@Error<>0 BREAK
if ( isnull(@c_Qty,0) > 0) and (@c_ItemNo is not null) begin
exec @x = Jeeves_Esales_AddOrderRow
@c_ForetagKod = @c_ForetagKod,
@c_OrderNumber = @c_OrderNumber,
@c_webUserName = @c_webUserName,
@c_PersSign = @c_PersSign,
@c_LangID = @c_LangID,
@c_ItemNo = @c_ItemNo,
@c_Qty = @c_Qty,
@c_AllocateAvailable = @c_AllocateAvailable,
@c_OverrideCreditLimit = @c_OverrideCreditLimit,
@c_RequestedDate = @c_RequestedDate,
@c_BatchId = @c_BatchId,
@c_ArtSerieNr = @c_ArtSerieNr,
@c_OrderType = @c_OrderType
if @x<0 Break
End
End
close cr_items
deallocate cr_items
End
if @c_Run_Type = 'R' exec Jeeves_Esales_GetOrp @c_ForetagKod,@c_webUserName,@LangID,@c_OrderNumber
Return isnull(@x,0)
End
if @c_qty <= 0 Return -210
select @Today = getdate()
if @c_RequestedDate is null select @c_RequestedDate = convert(nvarchar(10),getdate(),112)
---Get customer discount, pricelist, credit info from table kus
select @kus_kundrabatt = kundrabatt,
@kus_rabklass = rabklass,
@kus_Kreditsparr = kreditsparr,
@kus_kundsaldo = kus.kundsaldo,
@kus_ordsum = kus.ordsum,
@kus_kundkredlim = kus.kundkredlim,
@kus_momskod = kus.momskod,
@kus_offnr = offnr,
@Samfaktutskr = samfaktutskr,
@kus_KundRabattKod0 = kundrabattkod0,
@kus_KundRabattKod1 = kundrabattkod1,
@kus_KundRabattKod2 = kundrabattkod2,
@kus_PrisListaKundSpec = prislistakundspec,
@kus_KundKategoriKod = kundkategorikod,
@kus_tradecalcmarkup = tradekalkmarkup,
@kus_tradefsgmarkup = tradefsgmarkup,
@kus_AddArtEjAktiv = AddArtEjAktiv
from kus
where
ftgnr = @CompanyNo and
foretagkod = @c_ForetagKod
--If customer credit is blocked then abort
if @kus_kreditsparr = '1' begin
exec Jeeves_GT @wr output,@c_ForetagKod,@LangID,1344,'Kunden <20>r kreditsp<73>rrad'
EXECUTE Jeeves_RaisError 21001, @wr
return -1344
end
--Get Item discount and descriptions from table ar
select @ar_ArtProdKlass = ar.artprodklass,
@ar_artrabklass = ar.artrabklass,
@ar_artkundrabkod = ar.artkundrabkod,
@ar_OrdvRabKod = ar.ordvrabkod,
@ar_momskod = ar.momskod,
@ar_palaggdelforp = isnull(ar.palaggdelforp,0),
@ar_palaggdelforpbelopp = ar.palaggdelforpbelopp,
@ar_artfsgforp = artfsgforp,
@ar_lagstalle = lagstalle,
@ar_ordTyp = ordTyp,
@Artbeskr = ArtBeskr,
@Artbeskr2 = ArtBeskr2,
@AnskaffningTillv = ar.AnskaffningTillv
from ar
where
ar.artnr = @c_ItemNo and
ar.foretagkod = @c_ForetagKod
--Assign from sy1
select @sy1_PrisListaInklMoms = isnull(prislistainklmoms,0),
@sy1_momskod = momskod,
@sy1_lagstalle = sy1.lagstalle,
@sy1_artbeskr_2_orp = sy1.artbeskr_2_orp,
@sy1_rbn = sy1.sy1_rbn,
@sy1_PrisListaStaffling = PrisListaStaffling,
@sy1_KodAlternativEnhet = KodAlternativEnhet
from sy1
where
sy1.foretagkod = @c_ForetagKod
select @AltEnhetKod = @c_AltEnhetKod
--HACK: If ArtFsgForp is set, find matching AltEnhetKod
if isnull(@ar_artfsgforp,0) > 0 and @AltEnhetKod is NULL begin
if @sy1_KodAlternativEnhet = 2 begin
select @AltEnhetKod = xae.AltEnhetKod, @AltEnhetOmrFaktor = xae.AltEnhetOmrFaktor
from xare join xae
on (xare.AltEnhetKod = xae.AltEnhetKod)
where
xae.AltEnhetOmrFaktor = ROUND(@ar_artfsgforp, 6) and
xare.artnr = @c_ItemNo and
xare.foretagkod = @c_ForetagKod
if @AltEnhetKod is not NULL begin
--Abort from futher logic down the line
select @sy1_KodAlternativEnhet = 0
end
end
end
--Get Item alternative unit from table xae
if @sy1_KodAlternativEnhet = 1 begin
if @AltEnhetKod is not NULL begin
select @AltEnhetOmrFaktor = AltEnhetOmrFaktor
from xae
where
xae.AltEnhetKod = @AltEnhetKod and
xae.foretagkod = @c_ForetagKod
end else begin
select @AltEnhetKod = AltEnhetKod, @AltEnhetOmrFaktor = AltEnhetOmrFaktor
from xae
where
xae.AltEnhetOrderStd = 1 and
xae.foretagkod = @c_ForetagKod
end
end
--Get Item alternative unit from table xare
if @sy1_KodAlternativEnhet = 2 begin
if @AltEnhetKod is not NULL begin
select @AltEnhetOmrFaktor = AltEnhetOmrFaktor
from xare
where
xare.AltEnhetKod = @AltEnhetKod and
xare.artnr = @c_ItemNo and
xare.foretagkod = @c_ForetagKod
end else begin
select @AltEnhetKod = AltEnhetKod, @AltEnhetOmrFaktor = AltEnhetOmrFaktor
from xare
where
xare.AltEnhetOrderStd = 1 and
xare.artnr = @c_ItemNo and
xare.foretagkod = @c_ForetagKod
end
if @AltEnhetOmrFaktor is NULL begin
select @AltEnhetOmrFaktor = AltEnhetOmrFaktor
from xae
where
xae.AltEnhetKod = @AltEnhetKod and
xae.foretagkod = @c_ForetagKod
end
end
if @AltEnhetKod is not NULL and @AltEnhetOmrFaktor > 0 begin
--Set the value that was not provided
if isnull(@c_QtyAltEnh,0) = 0 and isnull(@c_Qty,0) != 0 begin
select @c_QtyAltEnh = @c_Qty / @AltEnhetOmrFaktor
end
if isnull(@c_Qty,0) = 0 and isnull(@c_QtyAltEnh,0) != 0 begin
select @c_Qty = @c_QtyAltEnh * @AltEnhetOmrFaktor
end
--If both Qty and QtyAltEnh is provided then verify correct value provided
if @c_Qty > 0 and @c_QtyAltEnh > 0 begin
if ROUND(@c_Qty, 4) != ROUND(@c_QtyAltEnh * @AltEnhetOmrFaktor, 4) begin
EXECUTE Jeeves_RaisError 21001, 'Qty and QtyAltEnh must follow the AltEnhetOmrFaktor when both are provided'
return 50100
end
end
end else begin
--Fallback if there is no alternative units
if isnull(@c_Qty,0) = 0 and isnull(@c_QtyAltEnh,0) != 0 begin
select @c_Qty = @c_QtyAltEnh
select @c_QtyAltEnh = 0
end
end
select @Enter_TranCount = @@TranCount,
@NewLine=char(13)+char(10),
@dbp=@@ProcId
--Calc pack markup
--TODO: Fix this, as somehow ar_artfsgforp was an int in this SP, which is wrong. And now this is broken.
if isnull(convert(integer,@ar_artfsgforp),0) > 0 begin
if ((select convert(integer,@c_Qty) % convert(integer,@ar_artfsgforp)) = 0)
select @ar_palaggdelforp = 0
end
if @c_OrderNumber is not NULL and @oh_OrdStat is NULL
select
@oh_OrdStat = [oh].[OrdStat],
@oh_vbordsum = oh.vbordsum,
@oh_ordsum = oh.ordsum,
@oh_lagstalle = oh.lagstalle
from [oh] where
[oh].[OrderNr] = @c_OrderNumber and
[oh].[ForetagKod] = @c_ForetagKod
---Use order type from ar if available
select @c_orderType = isnull(@c_orderType, @ar_ordTyp)
--Always use parameter if provided to the store procedure
--Use order location from ar table if present
select @c_Lagstalle = isnull(@c_Lagstalle,@ar_Lagstalle)
--Use order location as default
if @c_Lagstalle is null select @c_lagstalle = @oh_Lagstalle
declare
@QtyAvailable as decimal
exec Jeeves_Ar_Disp_Test
@c_IntrnCoNo = @c_ForetagKod,
@ArtNr = @c_ItemNo,
@OrdAntal = @c_Qty,
@OrdDatum = @c_RequestedDate,
@LagStalle = @c_LagStalle,
@c_Return_Type='Y',
@o_DispQty = @QtyAvailable output,
@o_FirstQty = @o_NextQty output,
@o_FirstDate = @o_NextDate output,
@o_LastQty = @o_LastQty output,
@o_LastDate = @o_LastDate output
if @o_NextQty is null begin
select @o_NextQty = 0
end
if @o_LastQty is null begin
select @o_LastQty = 0
end
-- if the AnskaffningTillv is larger than 0 (the product is produced by orders) then discard Jeeves_Ar_Disp_Test
if @AnskaffningTillv>0 begin
select @c_AllocateAvailable = 0
end
if @c_AllocateAvailable = 1 begin
-- Place the order with the amount available on the requested date and
-- return information about the rest.
if @c_RequestedDate = @o_NextDate begin
--If the first date and the requested date are the same,
--the on hand quantity will appear as first date quantity as well.
select @o_NextDate = null
select @o_NextQty = 0
end
--Prefer next to last.
if @o_NextQty = 0 or @o_NextQty is null begin
select @o_NextQty = @o_LastQty
select @o_NextDate = @o_LastDate
select @o_LastQty = 0
select @o_LastDate = null
end
--If there is nothing available on the requested date, use the next
if @QtyAvailable = 0 begin
select @c_Qty = @o_NextQty
select @DateAvailable = @o_NextDate
select @o_NextQty = @o_LastQty
select @o_NextDate = @o_LastDate
select @o_LastQty = 0
select @o_LastDate = null
end else begin
--Only the amount available on the requested date should be allocated on the order row.
select @DateAvailable = @c_RequestedDate
select @c_Qty = @QtyAvailable
end
if @o_NextQty = 0 begin
select @o_NextDate = null
end
if @o_LastQty = 0 begin
select @o_LastDate = null
end
end else begin
--If everything is available immediately, set detaavailable to now
if(@QtyAvailable >= @c_Qty) begin
select @DateAvailable = @c_RequestedDate
end else begin
--Everything isn't available, if last quantity is 0, the rest is available on first date.
if @o_LastQty = 0 begin
select @DateAvailable = @o_NextDate
end else begin
--leave last date as date available, sice the order can't be delivered before that date.
select @DateAvailable = @o_LastDate
end
end
select @o_NextQty = 0
select @o_NextDate = null
select @o_LastQty = 0
select @o_LastDate = null
end
--Pass the quantity that is placed on the order back to the caller.
select @o_AllocatedQty = @c_Qty
select @o_AllocatedDate = @DateAvailable
select @InkAvt = al.InkAvt, @LevNr=al.FtgNr , @ArtLevPrior = al.ArtLevPrior, @ArtHuvudAvt =al.ArtHuvudAvt from al where al.ArtNr=@c_ItemNo and al.ArtHuvudAvt='1' and al.ForetagKod=@c_ForetagKod;
--@In_SALES170 JEEVES_Boolean=NULL,
--@In_SALES190 smallint=NULL,
select @SALES170 = dbo.Jeeves_FN_GetParam(@c_ForetagKod, 'SALES170', 'B',null)
select @SALES190 = dbo.Jeeves_FN_GetParam(@c_ForetagKod, 'SALES190', 'N',null)
select @SALES048 = dbo.Jeeves_FN_GetParam(@c_ForetagKod, 'SALES048', 'N',null)
select @SALES007 = dbo.Jeeves_FN_GetParam(@c_ForetagKod, 'SALES007', 'N',null)
--Get cost codes from sales person or location (the same way as UF_Oh_Get_InternKonton)
if @SALES048 = 1 begin
--Get settings from inventory location
select
@KostStalleKod = KostStalleKod,
@KostBar = KostBar,
@K4 = K4,
@K5 = K5,
@K6 = K6,
@K7 = K7,
@AutoRegel = AutoRegelForskott
from xb with (NoLock) where
ForetagKod = @c_ForetagKod
and LagStalle = @c_LagStalle;
end else if @SALES048 = 2 begin
--Get settings from sales person
if @Saljare is not null begin
select
@KostStalleKod = KostStalleKod,
@KostBar = KostBar,
@K4 = K4,
@K5 = K5,
@K6 = K6,
@K7 = K7,
@AutoRegel = AutoRegelForskott
from salj with (NoLock) where
ForetagKod = @c_ForetagKod
and Saljare=@Saljare;
end
end else if @SALES048 = 3 begin
-- Try sales person first and then and add values
-- from location is not provided
if @Saljare is not null begin
select
@KostStalleKod = KostStalleKod,
@KostBar = KostBar,
@K4 = K4,
@K5 = K5,
@K6 = K6,
@K7 = K7,
@AutoRegel = AutoRegelForskott
from salj with (NoLock) where
ForetagKod = @c_ForetagKod
and Saljare=@Saljare;
end
select
@KostStalleKod = ISNULL(@KostStalleKod, KostStalleKod),
@KostBar = ISNULL(@KostBar, KostBar),
@K4 = ISNULL(@K4, K4),
@K5 = ISNULL(@K5, k5),
@K6 = ISNULL(@K6, K6),
@K7 = ISNULL(@K7, K7),
@AutoRegel = ISNULL(@AutoRegel, AutoRegelForskott)
from xb with (NoLock) where
ForetagKod = @c_ForetagKod
and LagStalle = @c_LagStalle;
end
--calculate fiscal year
declare @TodayDate datetime;
declare @redar integer;
declare @period integer;
declare @Just_BokfDat datetime;
set @TodayDate = convert(varchar(10), @Today, 112);
execute CalcPeriodP2P @todayDate,
null,
@redar output,
@period output,
@Just_BokfDat output,
@c_ForetagKod
declare @startRow int
declare @endRow int
declare @maxRow int -- The highest row number in the discount row range.
declare @discountQty decimal(17,6)
if @o_OrderRow is not null begin
--All rows that are grouped by being in the same decade should
--be updated with the discount, since they probably make up a split order row,
--and they should all be taken into account when calculating the discount.
--This is only relevant if a row number is provided.
select @startRow = 10 * (@o_OrderRow / 10) -- yx->y0, e.g. 24->20
select @endRow = @startRow + 9
--The updated row is excluded from the sum but the new quantity is added directly to discountQty
select @discountQty = (isnull(sum(ordantal), 0) + @c_Qty), @maxRow = isnull(max(OrdRadNr), @startRow) from orp
where ForetagKod = @c_ForetagKod and OrderNr = @c_OrderNumber and OrdRadNr >= @StartRow and OrdRadNr <= @EndRow
and OrdRadNr <> @o_OrderRow and ArtNr =@c_ItemNo
if @o_OrderRow > @maxRow begin
select @maxRow = @o_OrderRow
end
end else begin
select @discountQty = @c_qty
select @startRow = -1
end
if @withPriceCalc = 'Y' begin
declare @orp_ArtCirkaPris as Jeeves_Amount,
@orp_VolymRabatt as float,
@orp_KundRabatt as float,
@orp_Rabatt1 as float,
@orp_Rabatt2 as float,
@orp_Rabatt3 as float,
@orp_valkod as nvarchar(3),
@orp_valkurs as decimal(18,8),
@orp_ordDatum as datetime
execute @x = Jeeves_orp_produkt_pris
@c_Foretagkod = @c_ForetagKod,
@ArtNr = @c_ItemNo,
@OrdAntal = @discountQty,
@ValKurs = @ValKurs,
@ValKod = @valkod,
@PrisLista = @Prislista,
@OrdDatum = @Today,
@ArtProdKlass = @ar_ArtProdKlass,
@offradnr = -32000,
@ArtRabKlass = @ar_ArtRabKlass,
@RabKlass = @kus_RabKlass,
@OrdvRabKod = @ar_OrdvRabKod,
@ArtKundRabKod = @ar_ArtKundRabKod,
@KundRabattKod0 = @kus_KundRabattKod0,
@KundRabattKod1 = @kus_KundRabattKod1,
@KundRabattKod2 = @kus_KundRabattKod2,
@In_PrisListaStaffling = @sy1_PrisListaStaffling,
@In_PrisListaKundSpec = @kus_PrisListaKundSpec,
@In_KundKategoriKod = @kus_KundKategoriKod,
@KundRabatt = @kus_KundRabatt,
@In_InkAvt = @InkAvt,
@In_LevNr = @LevNr,
@In_ArtLevPrior = @ArtLevPrior,
@In_ArtHuvudAvt = @ArtHuvudAvt,
@In_tradekalkmarkup = @kus_tradecalcmarkup,
@In_tradefsgmarkup = @kus_tradefsgmarkup,
@In_OrdBerLevDat = @DateAvailable,
@In_Sales170 = @SALES170,
@In_Sales190 = @SALES190,
@In_RedovisnAr = @redar,
@in_FtgNr = @CompanyNo,
@Logg = null,
@offnr = @kus_offnr,
@In_LagStalle = @c_Lagstalle,
@O_Svar_Pris_Valuta = @orp_Vb_Pris output,
@O_Svar_VolymRabatt = @orp_VolymRabatt output,
@O_Svar_KundRabatt = @orp_KundRabatt output,
@O_Svar_Rabatt1 = @orp_Rabatt1 output,
@O_Svar_Rabatt2 = @orp_Rabatt2 output,
@O_Svar_Rabatt3 = @orp_Rabatt3 output,
@O_Svar_CirkaPris_Valuta = @orp_ArtCirkaPris output
if @ar_palaggdelforpbelopp = 1 begin
select @orp_vb_pris = @orp_vb_pris + @ar_palaggdelforp
end else begin
-- palaggdelforp is specified in percentage, so 80 should result in a factor of 1.8.
select @orp_vb_pris = @orp_vb_pris * (1 + 0.01 * @ar_palaggdelforp)
end
end
---Calc VAT------------------------------------------------------
if @kus_momskod = 0 begin
select @Momssats = 0
end
else begin
if @ar_momskod is null select @ar_momskod = isnull(@kus_momskod,@sy1_momskod)
select @Momssats = x1.momssats
from x1
where
momskod = @ar_momskod
end
if @sy1_PrisListaInklMoms in ('1','3') begin
select @orp_vb_prisinklmoms = @orp_Vb_Pris
select @orp_vb_pris = @orp_vb_pris/(1.0+(@MomsSats/100.0))
end
else begin
select @orp_vb_prisinklmoms = @orp_vb_pris * (1.0+(@Momssats/100.0))
end
-----------------------------------------------------------
---Check if creditlimit is reached
if @kus_kundkredlim > 0 and @c_OverrideCreditLimit = 0 begin
if (isnull(@oh_ordsum,0) + @kus_kundsaldo + @kus_ordsum + (@Orp_vb_pris*@c_qty)) > @kus_kundkredlim begin
exec Jeeves_GT @wr output,@c_ForetagKod,@LangID,3679,'Kundens kreditgr<67>ns <20>r <20>verskriden'
EXECUTE Jeeves_RaisError 21001, @wr
return -3679
end
end
begin tran
select @oh_vbordsum = isnull(@oh_vbordsum, 0) + (@c_qty * @orp_Vb_Pris * ((100 - @orp_Rabatt1)/100) * ((100 - @orp_Rabatt2)/100) * ((100 - @orp_Rabatt3)/100) * ((100-@orp_kundrabatt)/100))
select @oh_ordsum = 0
select @orp_ordradst = dbo.Jeeves_FN_GetParam (@c_ForetagKod,'WEBAPP018','N','10')
if @oh_OrdStat < @orp_ordradst select @orp_ordradst = @oh_OrdStat
if @sy1_artbeskr_2_orp = 1 begin
--Add description to orp row if artbeskr_2_orp flag is set.
-- First try the old way.
select @Artbeskr = isnull(ArtBeskr, @Artbeskr), @Artbeskr2 = isnull(ArtBeskr2, @Artbeskr2)
from arb where
foretagkod = @c_ForetagKod
and sprakkod = @LangID
and artnr = @c_ItemNo
--Override with modern translation with old transalation value as default
execute @ArtBeskr = Jeeves_FN_GetCustomerDropDownValue
@c_ForetagKod,
@LangID,
'ar',
@c_ItemNo,
@ArtBeskr
end
else begin
select @Artbeskr = null, @Artbeskr2 = null
end
if @c_Qty <= 0
begin
if @@TranCount > 0 ROLLBACK TRANSACTION
EXECUTE Jeeves_RaisError 50100, 'Jeeves_Esales_AddOrderRow'
while @@TranCount<@Enter_TranCount BEGIN TRANSACTION
return 50100
end
execute @x = Jeeves_Init_Insert_orp
@c_ForetagKod = @c_ForetagKod,
@c_OrderNr = @c_OrderNumber,
@c_FtgNr = @CompanyNo,
@c_ArtNr = @c_ItemNo,
@c_ArtSerieNr = @c_ArtSerieNr,
@c_BatchId = @c_BatchID,
@c_Edit = @c_Edit,
@c_EditExt = @c_EditExt,
@c_OrdBegLevDat = @c_RequestedDate,
@c_OrdBerLevDat = @DateAvailable,
@c_Lagstalle = @c_lagstalle,
@c_OrdAntal = @c_Qty,
@c_OrdAntalAltEnh = @c_QtyAltEnh,
@c_OrdTyp = @c_OrderType,
@c_ordradst = @orp_ordradst,
@c_PersSign = @c_PersSign,
@c_volymrabatt = @orp_VolymRabatt,
@c_KundRabatt = @orp_KundRabatt,
@c_Rabatt1 = @orp_Rabatt1,
@c_Rabatt2 = @orp_Rabatt2,
@c_Rabatt3 = @orp_Rabatt3,
@c_Saljare = @Saljare,
@c_vb_pris = @orp_Vb_Pris,
@c_vb_prisinklmoms = @orp_vb_prisinklmoms,
@c_rowcreatedby = @c_perssign,
@c_rowcreateddt = @Today,
@c_Valkod = @valkod,
@c_Valkurs = @valkurs,
@c_OrdRadNr = @o_OrderRow output,
@c_Artbeskr = @ArtBeskr,
@c_OrdArtBeskr = @ArtBeskr2,
@c_KostBar = @KostBar,
@c_KostStalleKod = @KostStalleKod,
@c_K4 = @K4,
@c_K5 = @K5,
@c_K6 = @K6,
@c_K7 = @K7,
@c_AutoRegel = @AutoRegel,
@c_AltEnhetKod = @AltEnhetKod;
if (@@Error <> 0) or (@x < 0) begin
EXECUTE Jeeves_RaisError 21001, 'Error when creating order row'
Rollback tran
Return -100
end
--If the row created with a specific row number, the prices of other rows might be affected.
if @startRow >= 0 begin
declare @rowIndex as int
select @rowIndex = @startRow
while @rowIndex <= @maxRow begin
Update orp SET
orp.vb_pris = @orp_Vb_Pris,
orp.vb_prisinklmoms = @orp_vb_prisinklmoms,
orp.volymrabatt = @orp_VolymRabatt,
orp.KundRabatt = @orp_KundRabatt,
orp.Rabatt1 = @orp_Rabatt1,
orp.Rabatt2 = @orp_Rabatt2,
orp.Rabatt3 = @orp_Rabatt3,
orp.rowupdatedby= @c_PersSign,
orp.rowupdateddt = getdate()
Where ordernr = @c_OrderNumber and
foretagkod = @c_ForetagKod and
ordradnr = @rowIndex and
artnr = @c_ItemNo and
ordradnr <> @o_OrderRow
select @rowIndex = @rowIndex + 1
end
end
update oh set
oh.ordsum = @oh_ordsum,
oh.vbordsum = @oh_vbordsum
where
oh.ordernr = @c_OrderNumber and
oh.foretagkod = @c_ForetagKod
if @@Error <> 0 begin
rollback tran
return -100
end
if @SALES007 = 1 AND @kus_AddArtEjAktiv <> '1'
BEGIN
exec JEEVES_Orp_Create_Tillagg @c_OrderNumber, @c_PersSign, @c_ForetagKod
END
if @@Error <> 0 begin
rollback tran
return -100
end
commit tran
--calculate ordervalue discount;
execute JEEVES_Oh_Kora @c_OrderNumber,NULL,2,null,@c_ForetagKod;
--make sure ordersum is updated;
execute JEEVES_Oh_OrderVarde @c_OrderNumber, @ValKurs, @sy1_rbn, @c_PersSign, @c_ForetagKod = @c_ForetagKod, @call_type = 4
if @c_Run_Type = 'R' exec Jeeves_Esales_GetOrp @c_ForetagKod,@c_webUserName,@LangID,@c_OrderNumber
return 0
GO
PRINT 'End Jeeves_Esales_AddOrderRow'
GO
PRINT 'End Jeeves_Esales_AddOrderRow'
GO

View file

@ -0,0 +1,340 @@
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO
SET NOCOUNT ON
GO
PRINT 'Begin Jeeves_Esales_CreateOrder'
GO
PRINT 'Begin Jeeves_Esales_CreateOrder'
PRINT '***************************************************'
PRINT '* *'
PRINT '* Jeeves_Esales_CreateOrder *'
PRINT '* *'
PRINT '***************************************************'
GO
IF OBJECT_ID( 'Jeeves_Esales_CreateOrder', 'P' ) IS NOT NULL
BEGIN
DROP PROCEDURE Jeeves_Esales_CreateOrder
END
GO
IF (SELECT COUNT(*) FROM xspr WHERE
SprTrgName = 'Jeeves_Esales_CreateOrder' AND
SprTrgType = 'P' ) > 0
BEGIN
UPDATE xspr SET beskrivning=beskrivning WHERE
SprTrgName = 'Jeeves_Esales_CreateOrder' AND
SprTrgType = 'P'
END ELSE BEGIN
INSERT INTO xspr(SprTrgName,SprTrgType,RowCreatedDt)
VALUES('Jeeves_Esales_CreateOrder' , 'P', GETDATE() )
END
GO
----------------------------------------------------
CREATE procedure Jeeves_Esales_CreateOrder
@c_ForetagKod smallint,
@c_webUserName Jeeves_StrVarChar64 = null,
@c_CompanyNo Jeeves_StrVarChar32 = null,
@c_PersSign Jeeves_StrVarChar6,
@c_LangID smallint = 0,
@c_BatchId Jeeves_StrVarChar16 = null,
@c_OrderType smallint = null,
@c_Run_Type char(1) = null,
@c_TemplateRowID int = null,
@c_Edit Jeeves_StrVarCharMax = null,
@c_EditExt Jeeves_StrVarCharMax = null,
@c_Saljare Jeeves_StrVarChar32 = null,
@c_Lagstalle Jeeves_StrVarChar10 = null,
@c_OverrideCreditLimit char(1) = 0,
@o_OrderNumber int = null output
as
set nocount on
--set ansi_warnings o f f
--set concat_null_yields_null o f f
--set ansi_nulls o f f
--set ansi_padding o f f
declare @withPriceCalc char(1)
select @withPriceCalc = 'Y'
declare @oh_OrdStat smallint,
@orp_OrdRadSt smallint,
@orp_OrdRadNr int,
@orp_vbordradsum money,
@orp_vb_prisinklmoms Jeeves_Amount,
@orp_Vb_Pris Jeeves_Amount,
@wr Jeeves_StrVarChar256,
@x int,
@dbe int,
@dbc int,
@dbp int,
@Enter_TranCount as int,
@NewLine as nvarchar(2),
@PgmId Jeeves_StrVarChar256,
@salj_saljare Jeeves_StrVarChar32,
@kus_kundrabatt float,
@kus_rabklass smallint,
@kus_Kreditsparr char(1),
@kus_kundsaldo money,
@kus_ordsum money,
@kus_kundkredlim money,
@kus_momskod smallint,
@kus_offnr Jeeves_StrVarChar10,
@ar_artkundrabkod char(1),
@ar_artrabklass smallint,
@ar_momskod smallint,
@sy1_PrisListaInklMoms char(1),
@sy1_momskod smallint,
@oh_vbordsum money,
@oh_ordsum money,
@Today datetime,
@Momssats float,
@CompanyNo Jeeves_StrVarChar20,
@ContactNo int,
@prislista int,
@lagstalle Jeeves_StrVarChar10,
@Saljare Jeeves_StrVarChar32,
@ar_artfsgforp smallint,
@ar_palaggdelforp float,
@ValKurs ztCurrencyRate,
@valkod Jeeves_StrVarChar4,
@Samfaktutskr char(1),
@Allowpurchase char(1),
@PCode int,
@SALES048 smallint,
@KostBar Jeeves_StrVarChar8,
@KostStalleKod Jeeves_StrVarChar8,
@K4 Jeeves_StrVarChar8,
@K5 Jeeves_StrVarChar8,
@K6 Jeeves_StrVarChar8,
@K7 Jeeves_StrVarChar8,
@AutoRegel Jeeves_StrVarChar4;
exec Jeeves_Esales_GetUserInfo
@c_IntrnCoNo = @c_ForetagKod,
@c_webUserName = @c_webUserName,
@c_CompanyNo = @c_CompanyNo,
@o_LangID = @c_LangID output,
@o_CompanyNo = @CompanyNo output,
@o_ContactNo = @ContactNo output,
@o_Lagstalle = @Lagstalle output,
@o_Prislista = @Prislista output,
@o_Saljare = @Saljare output,
@o_Valkod = @Valkod output,
@o_ValKurs = @Valkurs output
if @c_Saljare is null select @c_Saljare = @Saljare
select @Today = getdate()
---Get customer discount, pricelist, credit info from table kus
select @kus_kundrabatt = kundrabatt,
@kus_rabklass = rabklass,
@kus_Kreditsparr = kreditsparr,
@kus_kundsaldo = kus.kundsaldo,
@kus_ordsum = kus.ordsum,
@kus_kundkredlim = kus.kundkredlim,
@kus_momskod = kus.momskod,
@kus_offnr = offnr,
@Samfaktutskr = samfaktutskr
from kus
where
ftgnr = @CompanyNo and
foretagkod = @c_ForetagKod
--If customer credit is blocked then abort
if @kus_kreditsparr = '1' begin
exec Jeeves_GT @wr output,@c_ForetagKod,@c_LangID,1344,'Kunden är kreditspärrad'
EXECUTE Jeeves_RaisError 21001, @wr
return -1344
end
--Assign from sy1
select @sy1_PrisListaInklMoms = isnull(prislistainklmoms,0),
@sy1_momskod = momskod
from sy1
where
sy1.foretagkod = @c_ForetagKod
select @Enter_TranCount = @@TranCount,
@NewLine=char(13)+char(10),
@dbp=@@ProcId
---Calc VAT------------------------------------------------------
if @kus_momskod = 0 begin
select @Momssats = 0
end
else begin
if @ar_momskod is null select @ar_momskod = isnull(@kus_momskod,@sy1_momskod)
select @Momssats = x1.momssats
from x1
where
momskod = @ar_momskod
end
if @sy1_PrisListaInklMoms in ('1','3') begin
select @orp_vb_prisinklmoms = @orp_Vb_Pris
select @orp_vb_pris = @orp_vb_pris/(1.0+(@MomsSats/100.0))
end
else begin
select @orp_vb_prisinklmoms = @orp_vb_pris * (1.0+(@Momssats/100.0))
end
--select vbpris2=@orp_vb_pris,prisin=@orp_vb_prisinklmoms, moms=@Momssats, kmoms=@kus_momskod,armoms= @ar_momskod
-----------------------------------------------------------
---Check if creditlimit is reached
if @kus_kundkredlim > 0 and @c_OverrideCreditLimit = 0 begin
if (isnull(@oh_ordsum,0) + @kus_kundsaldo + @kus_ordsum) > @kus_kundkredlim begin
exec Jeeves_GT @wr output,@c_ForetagKod,@c_LangID,3679,'Kundens kreditgräns är överskriden'
EXECUTE Jeeves_RaisError 21001, @wr
return -3679
end
end
--Always use parameter if provided.
if @c_Lagstalle is null select @c_Lagstalle = @Lagstalle
select @SALES048 = dbo.Jeeves_FN_GetParam(@c_ForetagKod, 'SALES048', 'N',null)
--Get cost codes from sales person or location (the same way as UF_Oh_Get_InternKonton)
if @SALES048 = 1 begin
--Get settings from inventory location
select
@KostStalleKod = KostStalleKod,
@KostBar = KostBar,
@K4 = K4,
@K5 = K5,
@K6 = K6,
@K7 = K7,
@AutoRegel = AutoRegelForskott
from xb with (NoLock) where
ForetagKod = @c_ForetagKod
and LagStalle = @c_LagStalle;
end else if @SALES048 = 2 begin
--Get settings from sales person
if @Saljare is not null begin
select
@KostStalleKod = KostStalleKod,
@KostBar = KostBar,
@K4 = K4,
@K5 = K5,
@K6 = K6,
@K7 = K7,
@AutoRegel = AutoRegelForskott
from salj with (NoLock) where
ForetagKod = @c_ForetagKod
and Saljare=@Saljare;
end
end else if @SALES048 = 3 begin
-- Try sales person first and then and add values
-- from location is not provided
if @Saljare is not null begin
select
@KostStalleKod = KostStalleKod,
@KostBar = KostBar,
@K4 = K4,
@K5 = K5,
@K6 = K6,
@K7 = K7,
@AutoRegel = AutoRegelForskott
from salj with (NoLock) where
ForetagKod = @c_ForetagKod
and Saljare=@Saljare;
end
select
@KostStalleKod = ISNULL(@KostStalleKod, KostStalleKod),
@KostBar = ISNULL(@KostBar, KostBar),
@K4 = ISNULL(@K4, K4),
@K5 = ISNULL(@K5, k5),
@K6 = ISNULL(@K6, K6),
@K7 = ISNULL(@K7, K7),
@AutoRegel = ISNULL(@AutoRegel, AutoRegelForskott)
from xb with (NoLock) where
ForetagKod = @c_ForetagKod
and LagStalle = @c_LagStalle;
end
begin tran
select @oh_vbordsum = 0
select @oh_ordsum = 0
select @oh_OrdStat = Convert(int,dbo.Jeeves_FN_GetParam(@c_ForetagKod,'webapp031','N',null))
if @oh_ordstat is null select @oh_OrdStat = Convert(int,dbo.Jeeves_FN_GetParam(@c_ForetagKod,'WEBAPP003','N',null))
select @c_OrderType = isnull(@c_OrderType, convert(int,dbo.Jeeves_FN_GetParam(@c_ForetagKod,'WEBAPP008','N','1')))
if @samfaktutskr = '1' begin
select @samfaktutskr = samfaktutskr from x6
where x6.ordtyp = @c_OrderType and x6.foretagkod = @c_ForetagKod
end
if (@oh_OrdStat is null) or (@oh_OrdStat > 10) select @oh_OrdStat = 10
execute @x = Jeeves_Init_Insert_oh
@c_ForetagKod = @c_ForetagKod,
@c_OrderNr = @o_OrderNumber output,
@c_OrderNrAlfa = 'Internet',
@c_OrdStat = @oh_ordstat,
@c_PersSign = @c_PersSign,
@c_FtgNr = @CompanyNo,
@c_Ordtyp = @c_OrderType,
@c_Lagstalle = @c_Lagstalle,
@c_Saljare = @c_Saljare,
@c_KundRabatt = @kus_kundrabatt,
@c_ftgkontaktnr = @ContactNo,
@c_vbordsum = @oh_vbordsum,
@c_rowcreatedby = @c_perssign,
@c_rowcreateddt = @Today,
@c_Valkod = @valkod,
@c_Valkurs = @Valkurs,
@c_Samfaktutskr = @samfaktutskr,
@c_KostBar = @KostBar,
@c_KostStalleKod = @KostStalleKod,
@c_K4 = @K4,
@c_K5 = @K5,
@c_K6 = @K6,
@c_K7 = @K7,
@c_AutoRegel = @AutoRegel;
if (@@Error <> 0) or (@x < 0) begin
EXECUTE Jeeves_RaisError 21001, 'Error when creating order header'
rollback tran
return -100
end
select @orp_ordradst = dbo.Jeeves_FN_GetParam (@c_ForetagKod,'WEBAPP018','N','10')
if @oh_OrdStat < @orp_ordradst select @orp_ordradst = @oh_OrdStat
update oh set
oh.ordsum = @oh_ordsum,
oh.vbordsum = @oh_vbordsum
where
oh.ordernr = @o_OrderNumber and
oh.foretagkod = @c_ForetagKod
if @@Error <> 0 begin
rollback tran
return -100
end
commit tran
if @c_Run_Type = 'R' exec Jeeves_Esales_GetOrp @c_ForetagKod,@c_webUserName,@c_LangID,@o_OrderNumber
return
GO
PRINT 'End Jeeves_Esales_CreateOrder'
GO
PRINT 'End Jeeves_Esales_CreateOrder'
GO

View file

@ -0,0 +1,451 @@
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO
SET NOCOUNT ON
GO
PRINT 'Begin Jeeves_Esales_PlaceOrder'
GO
PRINT 'Begin Jeeves_Esales_PlaceOrder'
PRINT '***************************************************'
PRINT '* *'
PRINT '* Jeeves_Esales_PlaceOrder *'
PRINT '* *'
PRINT '***************************************************'
GO
IF OBJECT_ID( 'Jeeves_Esales_PlaceOrder', 'P' ) IS NOT NULL
BEGIN
DROP PROCEDURE Jeeves_Esales_PlaceOrder
END
GO
IF (SELECT COUNT(*) FROM xspr WHERE
SprTrgName = 'Jeeves_Esales_PlaceOrder' AND
SprTrgType = 'P' ) > 0
BEGIN
UPDATE xspr SET beskrivning=beskrivning WHERE
SprTrgName = 'Jeeves_Esales_PlaceOrder' AND
SprTrgType = 'P'
END ELSE BEGIN
INSERT INTO xspr(SprTrgName,SprTrgType,RowCreatedDt)
VALUES('Jeeves_Esales_PlaceOrder' , 'P', GETDATE() )
END
GO
----------------------------------------------------
CREATE Procedure Jeeves_Esales_PlaceOrder
@c_ForetagKod smallint,
@c_OrderNumber int output,
@c_webUserName Jeeves_StrVarChar64 = null,
@c_CompanyNo Jeeves_StrVarChar32 = null,
@c_LangID smallint = null,
@c_LevSattKod smallint = null,
@c_kundref2 Jeeves_StrVarChar32 = null,
@c_kundbestnr Jeeves_StrVarChar32 = null,
@c_editext Jeeves_StrVarCharMax = null,
@c_CoName Jeeves_StrVarChar64 = null,
@c_Addr1 Jeeves_StrVarChar64 = null,
@c_Addr2 Jeeves_StrVarChar64 = null,
@c_PostalCode Jeeves_StrVarChar64 = null,
@c_City Jeeves_StrVarChar64 = null,
@c_ProvinceCode Jeeves_StrVarChar5 = null,
@c_CountryCode Jeeves_StrVarChar3 = null,
@c_godsmarke1 Jeeves_StrVarChar64 = null,
@c_godsmarke2 Jeeves_StrVarChar64 = null,
@c_godsmarke3 Jeeves_StrVarChar64 = null,
@c_godsmarke4 Jeeves_StrVarChar64 = null,
@c_TA_MailNotified Jeeves_StrVarChar64 = null,
@c_TA_PhonNotifiedNo Jeeves_StrVarChar64 = null,
@c_TA_SMSNotifiedNo Jeeves_StrVarChar64 = null,
@c_levforetolv char(1) = '0',
@c_withFreightCalc char(1) = 'Y',
@c_ordlevplats1 Jeeves_StrVarChar20 = null,
@c_vRef Jeeves_StrVarChar64 = null,
@c_orderStatus smallint = null,
@c_PartDeliveryAllowed smallint = null,
@c_text Jeeves_StrVarCharMax = null,
@c_PaymentType char(1) = '0' --0=invoice, 1=card
As
set nocount on
declare @dbc int,
@ftgnr Jeeves_StrVarChar32,
@ftgkontaktnr int,
@org_ftgnr Jeeves_StrVarChar32,
@kp_ftgperson Jeeves_StrVarChar32,
@Momskod smallint,
@Levsattkod smallint,
@LevvillkKod smallint,
@Lagstalle Jeeves_StrVarChar10,
@Betkod Jeeves_StrVarChar2,
@Kreditorder Jeeves_Boolean,
@valkurs ztCurrencyRate,
@Ordsum Jeeves_Amount,
@PersSign Jeeves_StrVarChar32,
@Sy1_rbn char(1),
@sy1_FtgKontaktToErRef char(1),
@wi smallint,
@x7_ordstat smallint,
@wr Jeeves_StrVarChar256,
@kundref2 Jeeves_StrVarChar32,
@TA_MailNotified Jeeves_StrVarChar64,
@TA_PhonNotifiedNo Jeeves_StrVarChar64,
@TA_SMSNotifiedNo Jeeves_StrVarChar64,
/*Output från Jeeves_Calc_Frakt*/
@out_Fraktkostnad JEEVES_Amount,
@out_ExpeditionKostnad JEEVES_Amount,
@Out_Fraktdebkod JEEVES_Boolean,
@Out_Ordervarde JEEVES_Amount,
@Out_Fraktvikt float,
@jvss_OrderReservation char(1),
@kpwid integer,
@OrdBerLevDat DateTime,
@OrdBerednDat DateTime,
@SALES007 smallint,
@kus_AddArtEjAktiv char(1),
@OrdLevAdr4 Jeeves_StrVarChar64
exec Jeeves_Esales_GetUserInfo @c_IntrnCoNo = @c_ForetagKod,
@c_webUserName = @c_webUserName,
@c_CompanyNo = @c_CompanyNo,
@o_LangID = @c_LangID output,
@o_CompanyNo = @ftgnr output,
@o_ContactNo = @ftgkontaktnr output,
@o_OrgCompanyNo = @org_ftgnr output,
@o_kpwid = @kpwid output
if (@c_ordlevplats1 = @ftgnr) select @c_ordlevplats1 = null
if (@c_ordlevplats1 = '') select @c_ordlevplats1 = null
-- concatenate postal code and city for order unique adresses
select @OrdLevAdr4 = @c_PostalCode + ' ' + @c_City
select @c_City = null
select @kp_ftgperson = kp.ftgperson from kp
where
kp.foretagkod = @c_ForetagKod and
kp.ftgnr = @ftgnr and
kp.ftgkontaktnr = @ftgkontaktnr
select @Momskod = momskod,
@Levsattkod = isnull(@c_LevSattKod, levsattkod),
@Kreditorder = kreditorder,
@Lagstalle = lagstalle,
@valkurs = valkurs,
@Ordsum = ordsum,
@Levvillkkod = levvillkkod,
@Betkod = betkod,
@PersSign = perssign,
@Kundref2 = kundref2,
@c_vRef = isnull(@c_vRef, vref),
@TA_MailNotified = TA_MailNotified,
@TA_PhonNotifiedNo = TA_PhonNotifiedNo,
@TA_SMSNotifiedNo = TA_SMSNotifiedNo
from oh where
oh.ordernr = @c_OrderNumber and
oh.foretagkod = @c_ForetagKod
if @@Rowcount < 1 begin
exec Jeeves_GT @wr output,@c_ForetagKod,@c_LangID,1237,'Kundordern saknas'
EXECUTE Jeeves_RaisError 21001, @wr
return
end
select @x7_ordstat = dbo.Jeeves_FN_GetParam(@c_ForetagKod,'WEBAPP003','N',null)
--jeevesparamnumeric from jvss
-- where
-- jeevesparamname = N'WebOrderStatus' and
-- foretagkod = @c_ForetagKod
--Use passed in value if available
select @x7_ordstat = isnull(@c_orderStatus, @x7_ordstat)
select @jvss_OrderReservation = jvss.jeevesparamboolean from jvss
where
jvss.jeevesparamname = 'webapp010' and
jvss.foretagkod = @c_ForetagKod
--select @orp_ordradstat = dbo.Jeeves_FN_GetParam (@c_IntrnCoNo,'WEBAPP018','N','10')
select @Sy1_rbn = sy1_rbn,
@sy1_FtgKontaktToErRef=FtgKontaktToErRef
from sy1
where sy1.foretagkod = @c_ForetagKod
--if @c_kundref is null and FtgKontaktToErRef is true then insert Contact name as reference
if @c_kundref2 is null begin
if @sy1_FtgKontaktToErRef = '1' select @c_kundref2 = @kp_ftgperson
end
if @c_vRef is null begin
if (@org_ftgnr <> @ftgnr) begin
select @c_vRef = ftgnamn from fr
where fr.ftgnr = @org_ftgnr and
fr.foretagkod = @c_ForetagKod
End
End
execute @wi = Jeeves_oh_ordervarde
@In_OrderNr = @c_OrderNumber,
@In_ValKurs = @Valkurs,
@In_sy1_rbn = @Sy1_rbn,
@In_PersSign = @Perssign,
@Call_Type = 4,
@c_Foretagkod = @c_ForetagKod
--The estimated delivery date of the order will be set to the earliest delivery date of a row.
-- Also fetch OrdBerednDat to set base later
select @OrdBerLevDat = min(OrdBerLevDat), @OrdBerednDat = min(OrdBerednDat) from orp where foretagkod = @c_ForetagKod AND OrderNr = @c_OrderNumber
begin tran
exec @wi = Jeeves_oh_kora @c_OrderNumber,null,2,@c_ForetagKod
if @Wi < 0 begin
Rollback tran
return @Wi
end
select @SALES007 = dbo.Jeeves_FN_GetParam(@c_ForetagKod, 'SALES007', 'N',null)
select @kus_AddArtEjAktiv = AddArtEjAktiv from kus where foretagkod = @c_ForetagKod and ftgnr = @ftgnr
if @SALES007 = 1 AND @kus_AddArtEjAktiv <> '1'
BEGIN
exec @wi = JEEVES_Orp_Create_Tillagg @c_OrderNumber,@Perssign,@c_ForetagKod,0
if @Wi < 0 begin
Rollback tran
return @Wi
end
END
if @c_withFreightCalc = 'Y' Begin
declare @Artvikt float,
@Volume float,
@Fraktvikt float,
@Expavgkod Jeeves_Boolean,
@Fraktkostnader Jeeves_Amount,
@DispatchFeeExists smallint,
@valkod Jeeves_StrVarChar4,
@Fraktskrymmekod Jeeves_Boolean
select @Artvikt = artvikt,
@Volume = volume,
@Fraktkostnader=Emballagekostnad+fraktkostnman+ForsakringsKostnad+ovrfaktkostnad1+ovrfaktkostnad2,
@valkod = valkod,
@Expavgkod = expavgkd,
@Fraktskrymmekod = fraktskrymmekod,
@Fraktvikt = fraktvikt
from oh where
oh.ordernr = @c_OrderNumber and
oh.foretagkod = @c_ForetagKod
--Check that dispatch fee is defined in table fkst
--This check has been removed as described in 521932.
--select @DispatchFeeExists=faktkostntyp from fkst where
-- fkst.faktkostntyp = 4 and
-- fkst.foretagkod = @c_ForetagKod
if @@Rowcount < 1 begin
rollback tran
exec Jeeves_GT @wr output,@c_ForetagKod,@c_LangID,1333,'Expeditionsavgift kan ej debiteras, definition saknas i tabellen Fakturakostnader (fkst) !'
EXECUTE Jeeves_RaisError 21001, @wr
return
end
execute JEEVES_Calc_Frakt
@In_ForetagKod=@c_ForetagKod,
@In_FtgNr = @ftgnr,
@In_OrdLevPlats1 = @ftgnr,
@In_OrderNr = @c_OrderNumber,
@In_KreditOrder = @Kreditorder,
--@In_OrdBerLevDat datetime=NULL,
@In_LagStalle = @Lagstalle,
--@In_OrdLevNr smallint=NULL,
--@In_OrdStat smallint=NULL,
--@In_OrdFsNr smallint=NULL,
@In_FraktVikt = @Artvikt,
@In_AntalEURPall = 0,
@In_AntalOvrPall = 0,
@In_Volume = @Volume,
@In_LevForeTolv = @c_Levforetolv,
@In_FraktSkrymmeKod = @Fraktskrymmekod,
@In_LevSattKod = @Levsattkod,
@In_LevVillkKod = @Levvillkkod,
@In_ValKurs = @Valkurs,
@In_Valkod = @Valkod,
@In_ExpAvgKod = @ExpAvgkod,
@In_BetKod = @Betkod,
@In_MomsKod = @Momskod,
@In_OrderVarde = @Ordsum,
@Call_Type = 1,
@PersSign = @PersSign,
@In_OvrFaktKostnader = 0,
@Svar_FraktKostnad = @out_Fraktkostnad output,
@Svar_ExpeditionKostnad = @out_ExpeditionKostnad output,
@Svar_FraktdebKod = @Out_Fraktdebkod output,
@Svar_OrderVarde = @Out_Ordervarde output,
@Svar_Fraktvikt = @Out_Fraktvikt output
if @@Error <> 0 begin
rollback tran
return
end
End
if @c_PaymentType <> '1' begin
Update oh SET
oh.ordernralfa = 'Internet',
oh.ordstat = isnull(@x7_ordstat,13),
oh.kundref2 = @c_kundref2,
oh.kundbestnr = @c_kundbestnr,
oh.levforetolv = @c_levforetolv,
oh.levsattkod = @Levsattkod,
oh.ordberlevdat = @OrdBerLevDat,
oh.ohordberlevdatbase = @OrdBerLevDat, -- Fix base LevDat and BerednDat. Used when adding rows in GUI.
oh.ohordberedndatbase = @OrdBerednDat,
oh.ordlevadr1 = @c_CoName,
oh.ordlevadr2 = @c_Addr1,
oh.ordlevadr3 = @c_Addr2,
oh.ordlevadr4 = @OrdLevAdr4,
oh.ordlevadrbstort = @c_City,
oh.Ordlevadrlandskod = @c_CountryCode,
oh.ordlevadrprovincecode = @c_ProvinceCode,
oh.editext = @c_editext,
oh.edit = @c_text,
oh.bruttovikt = isnull(@artvikt,0),
oh.fraktkostnber = isnull(@out_Fraktkostnad,0),
oh.godsmarke1 = @c_godsmarke1,
oh.godsmarke2 = @c_godsmarke2,
oh.godsmarke3 = @c_godsmarke3,
oh.godsmarke4 = @c_godsmarke4,
oh.ordlevplats1 = @c_ordlevplats1,
oh.dellevtillaten = isnull(@c_PartDeliveryAllowed, 0),
oh.vref = @c_vRef,
oh.ta_mailnotified = isnull(@c_TA_MailNotified, @TA_MailNotified),
oh.ta_phonnotifiedno = isnull(@c_TA_PhonNotifiedNo, @TA_PhonNotifiedNo),
oh.ta_smsnotifiedno = isnull(@c_TA_SMSNotifiedNo, @TA_SMSNotifiedNo)
Where ordernr = @c_OrderNumber and
foretagkod = @c_ForetagKod
if @@Error <> 0 begin
rollback tran
return
end
update kp set
kp.antalkop = isnull(kp.antalkop,0) + 1
where
kp.foretagkod = @c_ForetagKod and
kp.ftgnr = @ftgnr and
kp.ftgkontaktnr = @ftgkontaktnr
if @@Error <> 0 begin
rollback tran
return
end
end else begin
--KORTBETALNING
--do not update orderstatus
--do not imcrement kp
Update oh SET
oh.ordernralfa = 'Internet',
oh.kundref2 = @c_kundref2,
oh.kundbestnr = @c_kundbestnr,
oh.levforetolv = @c_levforetolv,
oh.levsattkod = @Levsattkod,
oh.ordberlevdat = @OrdBerLevDat,
oh.ohordberlevdatbase = @OrdBerLevDat, -- Fix base LevDat and BerednDat. Used when adding rows in GUI.
oh.ohordberedndatbase = @OrdBerednDat,
oh.ordlevadr1 = @c_CoName,
oh.ordlevadr2 = @c_Addr1,
oh.ordlevadr3 = @c_Addr2,
oh.ordlevadr4 = @OrdLevAdr4,
oh.ordlevadrbstort = @c_City,
oh.Ordlevadrlandskod = @c_CountryCode,
oh.ordlevadrprovincecode = @c_ProvinceCode,
oh.editext = @c_editext,
oh.edit = @c_text,
oh.bruttovikt = isnull(@artvikt,0),
oh.fraktkostnber = isnull(@out_Fraktkostnad,0),
oh.godsmarke1 = @c_godsmarke1,
oh.godsmarke2 = @c_godsmarke2,
oh.godsmarke3 = @c_godsmarke3,
oh.godsmarke4 = @c_godsmarke4,
oh.ordlevplats1 = @c_ordlevplats1,
oh.dellevtillaten = isnull(@c_PartDeliveryAllowed, 0),
oh.vref = @c_vRef,
oh.ta_mailnotified = isnull(@c_TA_MailNotified, @TA_MailNotified),
oh.ta_phonnotifiedno = isnull(@c_TA_PhonNotifiedNo, @TA_PhonNotifiedNo),
oh.ta_smsnotifiedno = isnull(@c_TA_SMSNotifiedNo, @TA_SMSNotifiedNo)
Where ordernr = @c_OrderNumber and
foretagkod = @c_ForetagKod
if @@Error <> 0 begin
rollback tran
return
end
end
--remove any old
if exists(select 1 from webl where webl.ConnSQLIDkpw = @kpwid and
webl.ordernr = @c_OrderNumber ) begin
update webl set
ordernr = null
where
webl.ConnSQLIDkpw = @kpwid and
webl.ordernr = @c_OrderNumber
end
if @@Error <> 0 begin
rollback tran
return
end
/*
insert into webo (Foretagkod,
OrderNr,
RowCreatedBy,
Perssign,
RegDat,
RowCreatedDt)
values (@c_IntrnCoNo,
@o_OrderNo,
@Perssign,
@Perssign,
getdate(),
getdate())
if @@Error <> 0 begin
rollback tran
return
end */
commit tran
if @jvss_Orderreservation = '1' Begin
execute JEEVES_Oh_Bok
@OrderNr = @c_OrderNumber,
@PersSign = @PersSign,
@ForetagKod =@c_ForetagKod
End
--select @o_OrderNo = @Ordernr
return
GO
PRINT 'End Jeeves_Esales_PlaceOrder'
GO
PRINT 'End Jeeves_Esales_PlaceOrder'
GO

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
import logging

70
pyjeeves/config.py Normal file
View file

@ -0,0 +1,70 @@
from logging.config import dictConfig
import os
import yaml
config = {
'alembic': {
'script_location': 'migrations',
'sqlalchemy.url': ''},
'databases': {
'meta': {},
'raw': {}},
'logging': {
'formatters': {
'simpleFormatter': {
'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'}},
'handlers': {
'consoleHandler': {
'class': 'logging.StreamHandler',
'formatter': 'simpleFormatter',
'level': 'DEBUG',
'stream': 'ext://sys.stdout'},
'fileHandler': {
'class': 'logging.FileHandler',
'filename': 'pyjeeves.log',
'formatter': 'simpleFormatter',
'level': 'INFO'}},
'loggers': {
'PyJeeves': {'handlers': ['fileHandler'],
'level': 'DEBUG'},
'alembic': {'handlers': ['fileHandler'],
'level': 'INFO'},
'sqlalchemy': {'handlers': ['fileHandler'],
'level': 'WARN',
'qualname': 'sqlalchemy.engine'}},
'root': {'handlers': ['consoleHandler'], 'level': 'DEBUG'},
'version': 1},
'sync_interval': 60}
config['debug'] = os.getenv('ENVIRONEMENT') == 'DEV'
DB_CONTAINER = os.getenv('APPLICATION_DB_CONTAINER', 'db')
config['databases']['raw'] = {
'user': os.getenv('JEEVES_USER', 'jvsdbo'),
'pw': os.getenv('JEEVES_PW', ''),
'host': os.getenv('JEEVES_HOST', ''),
'port': os.getenv('JEEVES_PORT', 1433),
'db': os.getenv('JEEVES_DB', ''),
}
config['databases']['meta'] = {
'user': os.getenv('META_MYSQL_USER', 'pyjeeves'),
'pw': os.getenv('META_MYSQL_PW', ''),
'host': os.getenv('META_MYSQL_HOST', DB_CONTAINER),
'port': os.getenv('META_MYSQL_PORT', 3306),
'db': os.getenv('META_MYSQL_DB', 'pyjeeves'),
}
# DB_URI = 'postgresql://%(user)s:%(pw)s@%(host)s:%(port)s/%(db)s' % POSTGRES
config['alembic']['sqlalchemy.url'] = (
'mysql+pymysql://%(user)s:%(pw)s@%(host)s:%(port)s/%(db)s?charset=utf8mb4' %
config['databases']['meta'])
try:
with open("config.yml", 'r') as ymlfile:
file_config = yaml.load(ymlfile, Loader=yaml.FullLoader)
config = {**config, **file_config} # Syntax introduced in Python 3.5
except IOError:
pass
dictConfig(config['logging'])

83
pyjeeves/connector.py Normal file
View file

@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
"""
pyjeeves
~~~~~~~~~~~~~~~
Global objects
"""
from pyjeeves import logging, config
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
# from sqlalchemy.orm.exc import UnmappedClassError
from pymssql import OperationalError
from sqlservice import Database
logger = logging.getLogger("PyJeeves." + __name__)
class DBConnector(object):
"""This class is used to control the SQLAlchemy integration"""
def __init__(self, enabled_clients=['raw'], metadata=None):
logger.info("Creating engines and sessionmakers")
self.enabled_clients = enabled_clients
self.raw_db, self.raw_session, self.raw_engine = (
self.raw_client() if 'raw' in enabled_clients else {})
self.meta = (self.meta_session() if 'meta' in enabled_clients else {})
def callproc(self, procedure="", params=[]):
conn = self.raw_engine.raw_connection()
with conn.cursor() as cursor:
try:
retval = cursor.callproc(procedure, params)
try:
cursor.nextset()
retval = cursor.fetchall()
except OperationalError:
logger.debug("Executed statement has no resultset")
conn.commit()
finally:
conn.close()
return retval
def execute(self, operation=""):
conn = self.raw_engine
with conn.connection.cursor(as_dict=True) as cursor:
try:
cursor.execute(operation)
results = cursor.fetchall()
finally:
conn.close()
return results
def raw_client(self):
if 'raw' not in self.enabled_clients:
logger.error('Raw client is not enabled')
logger.info("Using DB %s" % config.config['databases']['raw']['db'])
uri = 'mssql+pymssql://{user}:{pw}@{host}:{port}/{db}?charset=utf8'.format(
**config.config['databases']['raw'])
db = Database(uri, echo=False)
return db, db.session(), db.connect()
def set_model_class(self, model_class):
self.raw_db.model_class = model_class
def meta_session(self):
meta_engine = create_engine(
'mysql+pymysql://{user}:{pw}@{host}:{port}/{db}?charset=utf8mb4'.format(
**config.config['databases']['meta']))
return scoped_session(sessionmaker(bind=meta_engine))

View file

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
from sqlalchemy import create_engine
from sqlalchemy.orm.session import Session
from models.jvsmodels import Base
class MySQLSession(Session):
"""docstring for MySQLSession"""
def __init__(self, settings):
self.engine = create_engine(
'mysql+pymysql://{user}:{passwd}@{host}:{port}/{db}?charset=utf8mb4'.format(**settings))
super(MySQLSession, self).__init__(bind=self.engine)
def create_db(self):
Base.metadata.create_all(self.engine)
if __name__ == '__main__':
import yaml
with open("config.yml", 'r') as ymlfile:
cfg = yaml.load(ymlfile)
session = MySQLSession(cfg['mysql'])
session.create_db()

12
pyjeeves/db_meta.py Normal file
View file

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
from pyjeeves import meta_engine
from models.meta import Base
if __name__ == '__main__':
from pyjeeves import logging
logger = logging.getLogger("PyJeeves." + __name__)
logger.info("Creating meta database")
Base.metadata.create_all(meta_engine)

View file

@ -7,14 +7,14 @@
""" """
import pymssql import pymssql
import datetime import datetime
import logging from pyjeeves import logging, config
class JvsQuery(): class JvsQuery():
"""JvsQuery based on http://pymssql.org/en/stable/ """ """JvsQuery based on http://pymssql.org/en/stable/ """
def __init__(self, settings): def __init__(self):
super(JvsQuery, self).__init__() super(JvsQuery, self).__init__()
self.settings = settings self.settings = config['jeeves_db']
self.logger = logging.getLogger("PyJeeves.jvsquery") self.logger = logging.getLogger("PyJeeves.jvsquery")
def _execute(self, query="", params=(), iterator=True): def _execute(self, query="", params=(), iterator=True):

View file

@ -1,19 +1,19 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import pprint import pprint
import yaml
import signal import signal
import sys import sys
import logging # import logging
import logging.config # import logging.config
from alembic.config import Config from alembic.config import Config
from alembic import command from alembic import command
from pyjeeves.connector import DBConnector
from pyjeeves import config
from process import Process from process import Process
from jvsquery import JvsQuery from jvsquery import JvsQuery
from db import MySQLSession
from utils import TaskThread from utils import TaskThread
@ -25,8 +25,9 @@ class SyncTread(TaskThread):
def __init__(self, config): def __init__(self, config):
super(SyncTread, self).__init__() super(SyncTread, self).__init__()
jvs_query = JvsQuery(config['jeeves_db']) # Use RawSession instead...
db_session = MySQLSession(config['mysql']) jvs_query = JvsQuery()
None, db_session = DBConnector.create_scoped_session(['meta'])
self.process = Process(jvs_query, db_session) self.process = Process(jvs_query, db_session)
self.logger = logging.getLogger("PyJeeves.SyncTread") self.logger = logging.getLogger("PyJeeves.SyncTread")
@ -38,16 +39,15 @@ class SyncTread(TaskThread):
if __name__ == '__main__': if __name__ == '__main__':
with open("config.yml", 'r') as ymlfile:
cfg = yaml.load(ymlfile)
logging.config.dictConfig(cfg['logging']) from pyjeeves import logging
# logging.config.dictConfig(config['logging'])
logger = logging.getLogger("PyJeeves") logger = logging.getLogger("PyJeeves")
logger.info("Running migrations") logger.info("Running migrations")
alembic_cfg = Config() alembic_cfg = Config()
for k in cfg['alembic']: for k in config['alembic']:
alembic_cfg.set_main_option(k, cfg['alembic'][k]) alembic_cfg.set_main_option(k, config['alembic'][k])
command.upgrade(alembic_cfg, "head") command.upgrade(alembic_cfg, "head")
logger.info("Application started") logger.info("Application started")
@ -59,9 +59,9 @@ if __name__ == '__main__':
signal.signal(signal.SIGINT, sigterm_handler) signal.signal(signal.SIGINT, sigterm_handler)
signal.signal(signal.SIGTERM, sigterm_handler) signal.signal(signal.SIGTERM, sigterm_handler)
sync_thread = SyncTread(cfg) sync_thread = SyncTread()
try: try:
sync_thread.setInterval(cfg['sync_interval']) sync_thread.setInterval(config['sync_interval'])
sync_thread.start() sync_thread.start()
sync_thread.join() sync_thread.join()
finally: finally:

View file

@ -3,7 +3,8 @@
pyjeeves.models pyjeeves.models
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
consolodated models module models for databases
""" """
from pyjeeves.connector import DBConnector
from .jvsmodels import * # noqa db = DBConnector()

224
pyjeeves/models/abc.py Normal file
View file

@ -0,0 +1,224 @@
"""
Define an Abstract Base Class (ABC) for models
"""
from decimal import Decimal
from datetime import datetime
from sqlalchemy.sql.expression import and_
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.exc import OperationalError
from sqlalchemy.schema import MetaData, Column
from sqlalchemy.types import Integer
from sqlalchemy.orm.collections import InstrumentedList
from sqlalchemy import event, orm
from sqlalchemy.orm import Session
from sqlservice import ModelBase, as_declarative
from pyjeeves import logging
from . import db
from .ext import install_validator_listner
logger = logging.getLogger("PyJeeves." + __name__)
logger.info("Reading Jeeves DB structure")
meta = MetaData()
try:
# TODO: Split raw.py and reflect tables on separate module loads?
meta.reflect(bind=db.raw_session.connection(),
only=['ar', 'ars', 'arsh', 'arean', 'xae', 'xare', 'fr', 'kus', 'x1k',
'oh', 'orp', 'lp', 'vg', 'xp', 'xm', 'prh', 'prl',
'kp', 'kpw', 'cr', 'X4', 'xw', 'X1', 'jfbs', 'lrfb',
'JAPP_EWMS_Item_Replenishment_Levels'])
except OperationalError as e:
logger.error("Failed to read Jeeves DB structure")
raise e
@event.listens_for(Session, "do_orm_execute")
def _add_filtering_criteria(execute_state):
"""Intercept all ORM queries. Add a with_loader_criteria option to all
of them.
This option applies to SELECT queries and adds a global WHERE criteria
(or as appropriate ON CLAUSE criteria for join targets)
to all objects of a certain class or superclass.
"""
# the with_loader_criteria automatically applies itself to
# relationship loads as well including lazy loads. So if this is
# a relationship load, assume the option was set up from the top level
# query.
# TODO: Make configurable if repo made pub
company_code = execute_state.execution_options.get("company_code", 1)
if (
not execute_state.is_column_load
and not execute_state.is_relationship_load
# and not execute_state.execution_options.get("include_private", False)
):
execute_state.statement = execute_state.statement.options(
orm.with_loader_criteria(
RawBaseModel,
lambda cls: cls.ForetagKod == company_code,
include_aliases=True,
)
)
@as_declarative(metadata=meta)
class RawBaseModel(ModelBase):
""" Generalize __init__, __repr__ and to_json
Based on the models columns , ForetagKod=1"""
__to_dict_filter__ = []
__to_dict_only__ = ()
__column_map__ = {}
__reversed_column_map__ = lambda self: {v: k for k, v in self.__column_map__.items()} # noqa
__table_args__ = {
'extend_existing': True
}
__dict_args__ = {
'adapters': {
datetime: lambda value, col, *_: value.strftime('%Y-%m-%d %H:%M'),
Decimal: lambda value, col, *_: float(value) # "{:.2f}".format(value)
}
}
ForetagKod = Column(Integer, primary_key=True)
def __init__(self, data=None, **kargs):
if data:
data = self._map_keys(data)
self.set(**kargs)
@classmethod
def _map_columns(cls, key):
if key in cls.__column_map__:
return cls.__column_map__[key]
return key
def _map_keys(self, data={}):
rv = {}
for key, value in self.__reversed_column_map__().items():
if key in data:
rv[value] = data[key]
for key, value in data.items():
if hasattr(self, key):
if key in self.relationships().keys():
rv[key] = self._map_relationship_keys(key, value)
else:
rv[key] = value
return rv
def _map_relationship_keys(self, field, value):
"""Get model relationships fields value. Almost a copy from SQLService ModelBase"""
relation_attr = getattr(self.__class__, field)
uselist = relation_attr.property.uselist
relation_class = relation_attr.property.mapper.class_
if uselist:
if not isinstance(value, (list, tuple)): # pragma: no cover
value = [value]
# Convert each value instance to relationship class.
value = [relation_class(val) if not isinstance(val, relation_class)
else val
for val in value]
elif value and isinstance(value, dict):
# Convert single value object to relationship class.
value = relation_class(value)
elif not value and isinstance(value, dict):
# If value is {} and we're trying to update a relationship
# attribute, then we need to set to None to nullify relationship
# value.
value = None
return value
def descriptors_to_dict(self):
"""Return a ``dict`` that maps data loaded in :attr:`__dict__` to this
model's descriptors. The data contained in :attr:`__dict__` represents
the model's state that has been loaded from the database. Accessing
values in :attr:`__dict__` will prevent SQLAlchemy from issuing
database queries for any ORM data that hasn't been loaded from the
database already.
Note:
The ``dict`` returned will contain model instances for any
relationship data that is loaded. To get a ``dict`` containing all
non-ORM objects, use :meth:`to_dict`.
Returns:
dict
"""
descriptors = self.descriptors()
return { # Expose hybrid_property extension
**{key: getattr(self, key) for key in descriptors.keys()
if isinstance(descriptors.get(key), hybrid_property)},
# and return all items included in descriptors
**{key: value for key, value in self.__dict__.items()
if key in descriptors}}
def to_dict(self):
rv = super().to_dict()
if self.__to_dict_only__:
return {
self._map_columns(key): rv[key]
for key in rv
if key in self.__to_dict_only__
}
for _filter in self.__to_dict_filter__:
rv.pop(_filter)
return rv
def from_dict(self, data={}):
for key, value in self.__reversed_column_map__().items():
if key in data:
self[value] = data[key]
for key, value in data.items():
if hasattr(self, key):
if isinstance(self[key], InstrumentedList):
pass
else:
self[key] = value
return self
def merge(self):
db.raw_session.merge(self)
return self
def commit(self):
db.raw_session.commit()
def save(self):
db.raw_session.add(self)
db.raw_session.commit()
return self
def delete(self):
db.raw_session.delete(self)
db.raw_session.commit()
# Apply validators for all string attributes in subclasses of RawBaseModel
@event.listens_for(RawBaseModel, 'attribute_instrument')
def receive_attribute_instrument(cls, key, inst):
"listen for the 'attribute_instrument' event"
install_validator_listner(cls, key, inst)
db.set_model_class(RawBaseModel)

56
pyjeeves/models/ext.py Normal file
View file

@ -0,0 +1,56 @@
from sqlalchemy.orm import ColumnProperty
from sqlalchemy.types import String
from sqlalchemy import event
class Error(Exception):
"""Base class for exceptions in this module."""
pass
class ValidationError(Error):
pass
class JeevesDBError(Error):
"""Exception raised for errors in the input.
Attributes:
expression -- input expression in which the error occurred
message -- explanation of the error
"""
def __init__(self, field, message):
super().__init__(message)
self.field = field
self.message = message
def install_validator_listner(class_, key, inst):
"""Add validators for any attributes that can be validated."""
prop = inst.prop
# Only interested in simple columns, not relations
if isinstance(prop, ColumnProperty) and len(prop.columns) == 1:
col = prop.columns[0]
# if we have string column with a length, create a length validator listner
if isinstance(col.type, String) and col.type.length:
event.listen(
getattr(class_, key), 'set', LengthValidator(
col.name, col.type.length), retval=True)
class LengthValidator():
def __init__(self, col_name, max_length):
self.col_name = col_name
self.max_length = max_length
def __call__(self, state, value, oldvalue, initiator):
if len(value) > self.max_length:
raise ValidationError(
"%s.%s: Length %d exceeds allowed %d" % (
state.__class__.__name__, state.__class__._map_columns(self.col_name),
len(value), self.max_length))
return value
# Add more validators, such as type for ints.

View file

@ -3,7 +3,7 @@
pyjeeves.models pyjeeves.models
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
Jeeves data models Jeeves meta data models
""" """
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base

587
pyjeeves/models/raw.py Normal file
View file

@ -0,0 +1,587 @@
# -*- coding: utf-8 -*-
"""
pyjeeves.models
~~~~~~~~~~~~~~~~~~~~~~
Jeeves raw data models
"""
from sqlalchemy.schema import ForeignKey, Column
from sqlalchemy.orm import relationship
from sqlalchemy.types import Integer, String
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.sql.expression import and_
from pyjeeves import logging
from .abc import RawBaseModel
from .sp_classes import OrderHead, OrderRow, PlaceOrder
import re
logger = logging.getLogger("PyJeeves." + __name__)
class ProductClass(RawBaseModel):
__tablename__ = 'xp'
__column_map__ = {'ArtProdKlass': 'ProductClassNumber', 'ArtProdklBeskr': 'ProductClassName'}
__to_dict_only__ = ('ArtProdKlass', 'ArtProdklBeskr')
# print_filter = ('Articles', 'articles_collection')
class ArticleClass(RawBaseModel):
__tablename__ = 'xm'
__column_map__ = {'ArtKod': 'ArticleClassNumber', 'ArtTypBeskr': 'ArticleClassName'}
__to_dict_only__ = ('ArtKod', 'ArtTypBeskr')
# print_filter = ('Articles', 'articles_collection')
class CommodityGroup(RawBaseModel):
__tablename__ = 'vg'
__column_map__ = {'VaruGruppKod': 'CommodityGroupNumber',
'VaruGruppBeskr': 'CommodityGroupName',
'ArticleClass': 'DefaultArticleClass'}
__to_dict_only__ = ('VaruGruppKod', 'VaruGruppBeskr', 'ArticleClass')
print_filter = ('Articles', 'articles_collection')
# to_json_filter = ('Articles', 'articles_collection')
ArtKod = Column(Integer, ForeignKey('xm.ArtKod'), primary_key=True)
ArticleClass = relationship(ArticleClass, lazy='joined')
class ArticleAlternativeUnit(RawBaseModel):
__tablename__ = 'xae'
__column_map__ = {'AltEnhetKod': 'UnitCode', 'AltEnhetBeskr': 'UnitName',
'AltEnhetOmrFaktor': 'DefaultUnitConv'}
__to_dict_only__ = ('AltEnhetBeskr', 'AltEnhetOmrFaktor')
class ArticleUnit(RawBaseModel):
__tablename__ = 'xare'
__column_map__ = {'ArtNr': 'ArticleNumber',
'AltEnhetKod': 'UnitCode', 'AltEnhetOmrFaktor': 'UnitConv',
'AltEnhetOrderStd': 'DefaultSalesUnit'}
__to_dict_only__ = ('AltEnhetKod', 'AltEnhetOmrFaktor',
'AltEnhetOrderStd', 'ArticleAlternativeUnit')
ArtNr = Column(String, ForeignKey('ar.ArtNr'), primary_key=True)
AltEnhetKod = Column(String, ForeignKey('xae.AltEnhetKod'), primary_key=True)
ArticleAlternativeUnit = relationship(ArticleAlternativeUnit, lazy='joined')
class ArticleBalance(RawBaseModel):
__tablename__ = 'ars'
__column_map__ = {'LagSaldo': 'Balance',
'LagResAnt': 'ReservedBalance',
'LagsaldoAltEnh': 'BalanceAlternative',
'LagResAntAltEnh': 'ReservedAlternativeBalance',
'LagStalle': 'StorageLocationNumber'}
__to_dict_only__ = ('LagSaldo',
'LagResAnt',
'LagsaldoAltEnh',
'LagResAntAltEnh',
'LagStalle')
# print_filter = ('Article', 'articles_collection')
# to_json_filter = ('Article', 'articles_collection')
ArtNr = Column(Integer, ForeignKey('ar.ArtNr'), primary_key=True)
class ArticleEAN(RawBaseModel):
__tablename__ = 'arean'
__column_map__ = {'ArtNrEAN': 'EAN', 'ArtNr': 'ArticleNumber'}
__to_dict_only__ = ('ArtNr', 'ArtNrEAN', 'ArticleUnit')
ArtNr = Column(String, ForeignKey('ar.ArtNr'), primary_key=True)
ArtNrEAN = Column(String, primary_key=True)
AltEnhetKod = Column(String, ForeignKey('xare.AltEnhetKod'))
ArticleUnit = relationship(ArticleUnit, lazy='joined')
class ArticleShelf(RawBaseModel):
__tablename__ = 'arsh'
__column_map__ = {'LagPlats': 'Shelf',
'LagStalle': 'WarehouseID',
'JAPP_EWMS_zoneid': 'WMSZoneID',
'ArtNr': 'ArticleNumber'}
__to_dict_only__ = ('LagPlats', 'LagStalle', 'JAPP_EWMS_zoneid', 'ArtNr')
LagPlats = Column(String, ForeignKey('ar.ArtNr'), primary_key=True)
LagStalle = Column(String, ForeignKey('ar.ArtNr'), primary_key=True)
class VATRate(RawBaseModel):
__tablename__ = 'X1'
__column_map__ = {'MomsKod': 'VATID', 'MomsSats': 'VATRate'}
__to_dict_only__ = ('MomsKod', 'MomsSats')
class Article(RawBaseModel):
__tablename__ = 'ar'
__column_map__ = {'ArtNr': 'ArticleNumber',
'ArtBeskr': 'ArticleName',
'ArtBeskrSpec': 'ArticleSpec',
'Edit': 'ArticleLongSpec',
'LagSaldoArtikel': 'UnitBalance',
'EnhetsKod': 'Unit',
# 'Extra1': 'WholeSaleUnit',
'ArtListPris': 'UnitListPrice'}
__to_dict_only__ = (
'ArtNr',
'ArtBeskr',
'ArtBeskrSpec',
'Edit',
'CommodityGroup',
'ProductClass',
'ArticleClass',
'ArticleBalance',
'EnhetsKod',
'LagSaldoArtikel',
'RowCreatedDt',
'ArtListPris',
'PictureFileName',
'UnitListPrice',
# 'Extra1',
'WholeSaleUnit',
'WholeSaleAmount',
'ListPrice',
'Balance',
'VATRate')
ArtNr = Column(Integer, primary_key=True)
VaruGruppKod = Column(Integer, ForeignKey('vg.VaruGruppKod'), primary_key=True)
ArtProdKlass = Column(Integer, ForeignKey('xp.ArtProdKlass'), primary_key=True)
ArtKod = Column(Integer, ForeignKey('xm.ArtKod'), primary_key=True)
MomsKod = Column(Integer, ForeignKey('X1.MomsKod'))
CommodityGroup = relationship(CommodityGroup, lazy='joined')
ProductClass = relationship(ProductClass, lazy='joined')
ArticleClass = relationship(ArticleClass, lazy='joined')
ArticleBalance = relationship(ArticleBalance)
ArticleUnit = relationship(ArticleUnit, uselist=True)
ArticleVATRate = relationship(VATRate, lazy='joined')
def _get_alt_unit(self):
# Find matching alternative unit for amount per piece, or return default for the article.
spec_conv = self.ArtFsgForp if self.ArtFsgForp else None
for unit in self.ArticleUnit:
if unit.AltEnhetOrderStd == "1" and not spec_conv:
return unit
elif (spec_conv and unit.AltEnhetOrder == "1" and (
unit.AltEnhetOmrFaktor == spec_conv or
unit.ArticleAlternativeUnit.AltEnhetOmrFaktor == spec_conv)):
return unit
def get_unit_conv(self):
# Return conversion factor for the article's alternative unit
if self.ArtFsgForp:
return self.ArtFsgForp
unit = self._get_alt_unit()
if unit:
return (unit.AltEnhetOmrFaktor if unit.AltEnhetOmrFaktor
else unit.ArticleAlternativeUnit.AltEnhetOmrFaktor)
return 1
@hybrid_property
def WholeSaleUnit(self):
# Description of alternative unit, or Extra1 if no alternative unit is in use.
unit = self._get_alt_unit()
if unit:
return unit.ArticleAlternativeUnit.AltEnhetBeskr
else:
return self.Extra1
@hybrid_property
def WholeSaleAmount(self):
# Amount of units in the alternative unit of the article, or 1 if none exist.
return self.get_unit_conv()
@hybrid_property
def VATRate(self):
return self.ArticleVATRate.MomsSats
@hybrid_property
def ListPrice(self):
try:
return self.ArtListPris * self.get_unit_conv()
except TypeError:
logger.debug("NoneType error, %s" % self.ArtNr)
@hybrid_property
def Balance(self):
try:
return self.LagSaldoArtikel / self.get_unit_conv()
except TypeError:
logger.debug("NoneType error, %s" % self.ArtNr)
class ContactInformationType(RawBaseModel):
__tablename__ = 'X4'
__to_dict_only__ = ('ComKod', 'ComBeskr')
class ContactInformation(RawBaseModel):
__tablename__ = 'cr'
__column_map__ = {'ComNr': 'Information'}
__to_dict_only__ = ('InformationType', 'ComNr')
FtgNr = Column(String, ForeignKey('fr.FtgNr'), primary_key=True)
FtgKontaktNr = Column(Integer, ForeignKey('kp.FtgKontaktNr'), primary_key=True)
ComKod = Column(String, ForeignKey('X4.ComKod'), primary_key=True)
ContactInformationType = relationship(ContactInformationType)
@hybrid_property
def InformationType(self):
return self.ContactInformationType.ComBeskr if self.ContactInformationType else ""
class ContactPerson(RawBaseModel):
__tablename__ = 'kp'
__column_map__ = {'FtgKontaktNr': 'ContactNumber', 'FtgPerson': 'ContactName'}
__to_dict_only__ = ('FtgKontaktNr', 'FtgPerson', 'ContactInformation')
FtgNr = Column(String, ForeignKey('fr.FtgNr'), primary_key=True)
FtgKontaktNr = Column(Integer, primary_key=True)
ContactInformation = relationship(
"ContactInformation", lazy='joined',
foreign_keys='ContactInformation.FtgNr, ContactInformation.FtgKontaktNr',
primaryjoin="and_(ContactInformation.FtgNr==ContactPerson.FtgNr,"
"ContactInformation.FtgKontaktNr==ContactPerson.FtgKontaktNr)")
class WebUser(RawBaseModel):
__tablename__ = 'kpw'
__to_dict_only__ = ('WebUserName', 'ValidToUse', 'ContactPersonName', 'ContactPersonNumber')
FtgNr = Column(String, ForeignKey('fr.FtgNr'), primary_key=True)
FtgKontaktNr = Column(Integer, ForeignKey('kp.FtgKontaktNr'), primary_key=True)
WebUserName = Column(String, primary_key=True)
ContactPerson = relationship(
ContactPerson, uselist=False, lazy='joined',
foreign_keys='ContactPerson.FtgNr, ContactPerson.FtgKontaktNr',
primaryjoin="and_(ContactPerson.FtgNr==WebUser.FtgNr,"
"ContactPerson.FtgKontaktNr==WebUser.FtgKontaktNr)")
@hybrid_property
def ContactPersonName(self):
return self.ContactPerson.FtgPerson if self.ContactPerson else ""
@hybrid_property
def ContactPersonNumber(self):
return self.ContactPerson.FtgKontaktNr if self.ContactPerson else ""
class LanguageCodes(RawBaseModel):
__tablename__ = 'xw'
SprakKod = Column(Integer, primary_key=True)
class Company(RawBaseModel):
__tablename__ = 'fr'
__column_map__ = {'FtgNr': 'CompanyNumber', 'FtgNamn': 'CompanyName',
'OrgNr': 'CompanyIdentityNumber', 'EUMomsNr': 'CompanyVATNumber',
'FtgPostAdr1': 'AddrCO', 'FtgPostadr5': 'AddrStreet',
'FtgLevPostNr': 'AddrPostalCode', 'FtgPostLevAdr3': 'AddrCity',
'LandsKod': 'AddrCountry',
'FtgPostAdr2': 'PostAddrStreet', 'FtgPostnr': 'PostAddrPostalCode',
'FtgPostAdr3': 'PostAddrCity'}
__to_dict_only__ = ('FtgNr', 'FtgNamn', 'Customer', 'ContactPersons', 'ContactInformation',
'FtgPostadr5', 'FtgPostAdr1', 'FtgPostLevAdr3', 'FtgLevPostNr', 'LandsKod',
'FtgPostAdr2', 'FtgPostnr', 'FtgPostAdr3', 'OrgNr', 'EUMomsNr')
FtgNr = Column(String, primary_key=True)
Customer = relationship('Customer', uselist=False, back_populates='Company', lazy='joined')
ContactPersons = relationship("ContactPerson", lazy='joined')
ContactInformation = relationship(
"ContactInformation", lazy='joined',
foreign_keys='ContactInformation.FtgNr',
primaryjoin="and_(ContactInformation.FtgNr==Company.FtgNr,"
"ContactInformation.FtgKontaktNr==null())")
class DelivLoc(RawBaseModel):
__tablename__ = 'lp'
class CustomerCategory(RawBaseModel):
__tablename__ = 'x1k'
KundKategoriKod = Column(Integer, primary_key=True)
class Customer(RawBaseModel):
__tablename__ = 'kus'
__column_map__ = {'FtgNr': 'CompanyNumber', 'kundkategorikod': 'CustomerCategoryCode',
'PrisListaKundSpec': 'PriceListPrimary', 'PrisLista': 'PriceListSecondary',
'GodsMarke1': 'DefaultShippingInfo', 'GodsMarke2': 'InternalInfo',
'LevSattKod': 'DefaultShippingTypeCode', 'ValKod': 'CustomerCurrency'}
__to_dict_only__ = ('kundkategorikod', 'PriceList', 'PriceListCommon', 'CustomerCategory',
'PrisLista', 'PrisListaKundSpec', 'WebUsers', 'GodsMarke1',
'GodsMarke2', 'LevSattKod', 'Language', 'ValKod')
FtgNr = Column(String, ForeignKey('fr.FtgNr'), primary_key=True)
SprakKod = Column(Integer, ForeignKey('xw.SprakKod'))
PrisLista = Column(Integer, ForeignKey('prh.PrisLista'))
PrisListaKundSpec = Column(Integer, ForeignKey('prh.PrisLista'))
Company = relationship("Company", back_populates="Customer")
PriceList = relationship("PriceList", uselist=False,
lazy='joined', foreign_keys='PriceList.FtgNr')
PriceListCommon = relationship("PriceList", uselist=False,
foreign_keys='PriceList.PrisLista',
primaryjoin="Customer.PrisLista==PriceList.PrisLista")
KundKategori = relationship(
"CustomerCategory", uselist=False, foreign_keys='CustomerCategory.KundKategoriKod',
primaryjoin="Customer.kundkategorikod==CustomerCategory.KundKategoriKod")
LanguageCodes = relationship("LanguageCodes")
WebUsers = relationship(
WebUser, uselist=True, lazy='joined',
foreign_keys='WebUser.FtgNr', primaryjoin="Customer.FtgNr==WebUser.FtgNr")
@hybrid_property
def CustomerCategory(self):
return self.KundKategori.KundKatBeskr if self.KundKategori else ""
@hybrid_property
def Language(self):
return self.LanguageCodes.Sprak if self.LanguageCodes else ""
class PriceList(RawBaseModel):
__tablename__ = 'prh'
__column_map__ = {'PrisListaBeskr': 'Description', 'PrisLista': 'PriceListNumber',
'MarkUpBelopp': 'PriceFactor'}
__to_dict_only__ = ('PrisListaBeskr', 'PrisLista', 'PriceListItems', 'MarkUpBelopp')
PrisLista = Column(Integer, primary_key=True)
FtgNr = Column(String, ForeignKey('kus.FtgNr'))
Customer = relationship('Customer', uselist=False, foreign_keys='Customer.PrisListaKundSpec')
PriceListItems = relationship('PriceListItem', back_populates="PriceList", lazy='joined')
class PriceListItem(RawBaseModel):
__tablename__ = 'prl'
__column_map__ = {'ArtNr': 'ArticleNumber', 'vb_pris': 'UnitPrice',
'MarkUpBelopp': 'UnitPriceFactor', 'KodNollPris': 'NullPriceAllowed'}
__to_dict_only__ = ('ArtNr', 'vb_pris', 'MarkUpBelopp', 'KodNollPris', 'Price')
__to_dict_filter__ = ['PriceList']
# Do not serialize price list relationship
__dict_args__ = {
'adapters': {
**{
PriceList: None
},
**RawBaseModel.__dict_args__['adapters']
}
}
PrisLista = Column(Integer, ForeignKey('prh.PrisLista'), primary_key=True)
ArtNr = Column(Integer, ForeignKey('ar.ArtNr'), primary_key=True)
PriceList = relationship('PriceList', uselist=False)
Article = relationship(Article)
# TODO: Could likely be optimized by getting all articles in one query and mangled in repo
@hybrid_property
def Price(self):
if not self.vb_pris and not self.MarkUpBelopp:
if self.KodNollPris == "1":
return 0
MarkUpBelop = self.PriceList.MarkUpBelopp if self.PriceList.MarkUpBelopp else 0
return (
(self.Article.ArtListPris + MarkUpBelop) *
self.Article.get_unit_conv())
if self.vb_pris:
return self.vb_pris * self.Article.get_unit_conv()
else:
return (
(self.Article.ArtListPris + self.MarkUpBelopp) *
self.Article.get_unit_conv())
class Order(RawBaseModel):
__tablename__ = 'oh'
__column_map__ = {'OrderNr': 'OrderNumber', 'FtgNr': 'CompanyNumber',
'OrdDatum': 'OrderDate', 'OrdStat': 'OrderStatusCode',
'OrdLevAdr1': 'AddrName', 'OrdLevAdr2': 'AddrCO',
'OrdLevAdr3': 'AddrStreet', 'OrdLevAdrLandsKod': 'AddrCountry',
'KundBestNr': 'CustomerContact', 'KundRef2': 'CustomerReference',
'GodsMarke1': 'ShippingInfo', 'GodsMarke2': 'InternalInfo',
'TA_MailNotified': 'ShippingEmail', 'TA_PhonNotifiedNo': 'ShippingPhone',
'TA_SMSNotifiedNo': 'ShippingSMS', 'LevSattKod': 'ShippingTypeCode',
'OrdBerLevDat': 'RequestedDeliveryDate'}
__to_dict_only__ = ('OrderNr', 'FtgNr', 'OrdDatum', 'OrdStat', 'CompanyName', 'LevSattKod',
'OrdLevAdr1', 'OrdLevAdr2', 'OrdLevAdr3', 'OrdBerLevDat',
'OrdLevAdrLandsKod', 'KundBestNr', 'KundRef2', 'GodsMarke1',
'GodsMarke2', 'OrderItems', 'AddrPostalCode', 'AddrCity',
'TA_MailNotified', 'TA_PhonNotifiedNo', 'TA_SMSNotifiedNo')
__dict_args__ = {
'adapters': {
**{
'OrdDatum': lambda ord_date, *_: ord_date.strftime("%Y-%m-%d"),
'OrdBerLevDat': lambda deliv_date, *_: (
deliv_date.strftime("%Y-%m-%d")
if not isinstance(deliv_date, str) else deliv_date),
},
**RawBaseModel.__dict_args__['adapters']
}
}
OrderNr = Column(Integer, primary_key=True)
FtgNr = Column(String, ForeignKey('fr.FtgNr'))
Company = relationship('Company', uselist=False)
OrderItems = relationship('OrderItem', uselist=True, back_populates="Order", lazy='joined')
@hybrid_property
def CompanyName(self):
return self.Company.FtgNamn if self.Company else ""
@CompanyName.setter
def CompanyName(self, value):
return
@hybrid_property
def AddrPostalCode(self):
if not self.OrdLevAdr4:
return
s = re.split('(?!\d)\s(?!\d)', self.OrdLevAdr4)
return s[0] if len(s) > 1 else ''
@AddrPostalCode.setter
def AddrPostalCode(self, value):
self.OrdLevAdr4 = value + (self.OrdLevAdr4 if self.OrdLevAdr4 else '')
@hybrid_property
def AddrCity(self):
if not self.OrdLevAdr4:
return
s = re.split('(?!\d)\s(?!\d)', self.OrdLevAdr4, maxsplit=1)
return s[1] if len(s) > 1 else ''
@AddrCity.setter
def AddrCity(self, value):
self.OrdLevAdr4 = (self.OrdLevAdr4 if self.OrdLevAdr4 else '') + ' ' + value
def create(self, webusername=None):
# TODO: Extend with additional functionlity if desired.
self['OrderNr'], invoicing_possible = OrderHead(
self['FtgNr'], webusername, pers_sign=self['PersSign']).callproc()
return self, invoicing_possible
def save(self, invoiced=False, webusername=None):
payment_method = 'invoice'
if not invoiced:
payment_method = 'card'
PlaceOrder(
self['FtgNr'], self['OrderNr'], webusername,
payment_method, data=self.to_dict()).callproc()
return self
class OrderItem(RawBaseModel):
__tablename__ = 'orp'
__column_map__ = {'OrdRadNr': 'OrderRowNumber', 'vb_pris': 'UnitPrice',
'ArtNr': 'ArticleNumber', 'OrdAntal': 'UnitAmount',
'OrdAntalAltEnh': 'AltUnitAmount', 'AltEnhetKod': 'AltUnit',
'OrdBegLevDat': 'RequestedDeliveryDate'}
__to_dict_only__ = ('OrdRadNr', 'vb_pris', 'ArtNr', 'ArticleName', 'OrdAntal',
'OrdAntalAltEnh', 'AltEnhetKod', 'OrdBegLevDat')
# Do not serialize order relationship
__dict_args__ = {
'adapters': {
**{
Order: None,
'OrdBegLevDat': lambda req_date, *_: (
req_date.strftime("%Y-%m-%d")
if req_date and not isinstance(req_date, str)
else req_date),
},
**RawBaseModel.__dict_args__['adapters']
}
}
OrderNr = Column(Integer, ForeignKey('oh.OrderNr'), primary_key=True)
OrdRadNr = Column(Integer, primary_key=True)
OrdRadNrStrPos = Column(Integer, primary_key=True)
OrdRestNr = Column(Integer, primary_key=True)
ArtNr = Column(Integer, ForeignKey('ar.ArtNr'))
Order = relationship('Order', uselist=False)
Article = relationship(Article)
@hybrid_property
def ArticleName(self):
return self.Article.ArtBeskr if self.Article else ""
@ArticleName.setter
def ArticleName(self, value):
return
def save(self):
# TODO: Additional information may be returned if desired.
row_no = OrderRow(
company_no=self['FtgNr'], order_no=self['OrderNr'], item_no=self['ArtNr'],
qty=self['OrdAntal'], qty_alt_unit=self['OrdAntalAltEnh'],
alt_unit=self['AltEnhetKod'], requested_date=self['OrdBegLevDat'],
pers_sign=self['PersSign']).callproc()
self['OrdRadNr'] = row_no
return self
class ItemReplenishmentLevels(RawBaseModel):
# __table_args__ = {'mssql_autoincrement': False, 'extend_existing': True}
# __table_args__ = {'implicit_returning': False, 'extend_existing': True}
__tablename__ = 'JAPP_EWMS_Item_Replenishment_Levels'
__column_map__ = {'ArtNr': 'ArticleNumber', 'LagPlats': 'Shelf', 'LagStalle': 'WarehouseID'}
__to_dict_only__ = ('LagPlats', 'ArtNr')
# Workaround for:
# "Table 'JAPP_EWMS_Item_Replenishment_Levels' does not have the identity property.
# Cannot perform SET operation."
ForetagKod = Column(Integer, primary_key=True, autoincrement=False)
class SupplierInvoicePayment(RawBaseModel):
__tablename__ = 'lrfb'
# __column_map__ = {'AltEnhetKod': 'UnitCode', 'AltEnhetBeskr': 'UnitName',
# 'AltEnhetOmrFaktor': 'DefaultUnitConv'}
# __to_dict_only__ = ('AltEnhetBeskr', 'AltEnhetOmrFaktor')
class SupplierInvoiceJournal(RawBaseModel):
__tablename__ = 'jfbs'
# __column_map__ = {'ArtNr': 'ArticleNumber',
# 'AltEnhetKod': 'UnitCode', 'AltEnhetOmrFaktor': 'UnitConv',
# 'AltEnhetOrderStd': 'DefaultSalesUnit'}
# __to_dict_only__ = ('AltEnhetKod', 'AltEnhetOmrFaktor',
# 'AltEnhetOrderStd', 'ArticleAlternativeUnit')
# ArtNr = Column(String, ForeignKey('ar.ArtNr'), primary_key=True)
# AltEnhetKod = Column(String, ForeignKey('xae.AltEnhetKod'), primary_key=True)
# ArticleAlternativeUnit = relationship(ArticleAlternativeUnit, lazy='joined')

View file

@ -0,0 +1,277 @@
# -*- coding: utf-8 -*-
import pymssql
from collections import OrderedDict
from pyjeeves.models import db
from pyjeeves import logging
from datetime import date
# from datetime import datetime
# from decimal import Decimal
logger = logging.getLogger("PyJeeves." + __name__)
class StoredProcedure(OrderedDict):
__raw_params = {}
# https://www.mssqltips.com/sqlservertip/1669/generate-a-parameter-list-for-all-sql-server-stored-procedures-and-functions/ # noqa
query = """SELECT SCHEMA_NAME(SCHEMA_ID) AS [Schema],
SO.name AS [ObjectName],
SO.Type_Desc AS [ObjectType (UDF/SP)],
P.parameter_id AS [ParameterID],
P.name AS [ParameterName],
TYPE_NAME(P.user_type_id) AS [ParameterDataType],
P.max_length AS [ParameterMaxBytes],
P.is_output AS [IsOutPutParameter]
FROM sys.objects AS SO
INNER JOIN sys.parameters AS P
ON SO.OBJECT_ID = P.OBJECT_ID
WHERE (
SO.name LIKE '%Jeeves_Esales_%' OR
SO.name LIKE '%JAPP_spr_LogTrade_%'
) AND
SO.OBJECT_ID IN ( SELECT OBJECT_ID
FROM sys.objects
WHERE TYPE IN ('P','FN'))
ORDER BY [Schema], SO.name, P.parameter_id"""
logger.debug("Getting information about stored procedures from database")
for param in db.execute(query):
if param['ObjectName'] not in __raw_params:
__raw_params[param['ObjectName']] = OrderedDict()
param_name = param['ParameterName'][1:]
__raw_params[param['ObjectName']][param_name] = param
@classmethod
def get_params_for(cls, procedure_name):
rv = OrderedDict()
for key in cls.__raw_params[procedure_name]:
param = cls.__raw_params[procedure_name][key]
if 'int' in param['ParameterDataType'].lower():
param_type = int
elif ('money' in param['ParameterDataType'].lower() or
'decimal' in param['ParameterDataType'].lower() or
'float' in param['ParameterDataType'].lower() or
'qty' in param['ParameterDataType'].lower()):
param_type = float
else:
# TODO: Format datetime and perhaps decimal?
param_type = str
if param['IsOutPutParameter'] == 1:
param_type = pymssql.output(param_type)
else:
param_type = param_type()
rv[key] = param_type
return rv
def __init__(self, procedure_name):
super(StoredProcedure, self).__init__()
self.procedure = procedure_name
self.update(StoredProcedure.get_params_for(self.procedure))
def _set_output(self, data=(), ret_resultset=False):
if ret_resultset:
return data
if len(self) != len(data):
raise
for p, k in enumerate(self):
if isinstance(self[k], pymssql.output):
self[k] = data[p]
return self
# Should the original object be unmodified? Return a new object:
# return [(k, data[p]) for p, k in enumerate(self)]
def callproc(self, resultset=False):
return self._set_output(db.callproc(
self.procedure,
self.values()),
resultset)
def values(self):
return [value if value else None
for value in super(StoredProcedure, self).values()]
def __setitem__(self, key, obj):
if (key in self and type(self[key]) is not type(obj) and
obj is not None and not isinstance(self[key], pymssql.output)):
raise TypeError
super(StoredProcedure, self).__setitem__(key, obj)
class OrderHead(StoredProcedure):
"""Mapping for the Jeeves_Esales_CreateOrder stored procedure parameters
webapp031 and WEBAPP003 determines default order status"""
# TODO: Extend with additional functionlity if desired.
def __init__(self, company_no, web_user_name, pers_sign='biz'):
super(OrderHead, self).__init__('Jeeves_Esales_CreateOrder')
self['c_CompanyNo'] = company_no
# Some defaults:
self['c_ForetagKod'] = 1 # Hardcoded to LK
self['c_PersSign'] = str(pers_sign) # From API profile, or default
# self['c_OrderType'] = None # Default set by WEBAPP008
# self['c_TemplateRowID'] = None # No template used
# self['c_Saljare'] = None # 600 # From API profile, or default
# Unique ID added to 'kpw' when invoicing is allowed.
self['c_webUserName'] = web_user_name
# self['LangID'] = 0 # Default to Swedish
# self['BatchId'] = '' # unused
# self['Run_Type'] = None # Could be 'R', but doesn't work
# self['Edit'] = None # Custom ordertext, currently not used in procedure
# self['EditExt'] = None # Custom ordertext, currently not used in procedure
# self['Lagstalle'] = None # '1' # Used to override customer default
# self['OverrideCreditLimit'] = 0 # Set to a char to override credit limit
# self['OrderNumber'] = pymssql.output(int)
def callproc(self):
try:
super(OrderHead, self).callproc()
except pymssql.DatabaseError:
logger.error("A DatabaseException has been caught. Order could not be created..")
# If call succeeded, then order is allowed to be invoiced.
return self['o_OrderNumber'], bool(self['c_webUserName'])
class OrderRow(StoredProcedure):
"""Mapping for the Jeeves_Esales_AddOrderRow stored procedure parameters
AltEnhetKod logic needs to have been added to the procedure"""
def __init__(self, company_no, order_no, item_no,
qty=None, qty_alt_unit=None, alt_unit=None,
requested_date=None, pers_sign='biz'):
super(OrderRow, self).__init__('Jeeves_Esales_AddOrderRow')
self['c_CompanyNo'] = str(company_no)
self['c_OrderNumber'] = int(order_no)
self['c_ItemNo'] = str(item_no)
self['c_Qty'] = float(qty) if qty else None
self['c_QtyAltEnh'] = float(qty_alt_unit) if qty_alt_unit else None
self['c_AltEnhetKod'] = str(alt_unit) if alt_unit else None
self['c_PersSign'] = str(pers_sign)
# Used to set date for delivery (c_OrdBegLevDat) and (c_OrdBerLevDat)
self['c_RequestedDate'] = (
requested_date.strftime('%Y%m%d')
if isinstance(requested_date, date) else requested_date)
# Some defaults:
self['c_ForetagKod'] = 1 # Hardcoded to LK
# self['OrderNumber'] = 0 # Required, ordernumber to add row to
# self['webUserName'] = order_head['webUserName']
# self['CompanyNo'] = order_head['CompanyNo']
# self['PersSign'] = order_head['PersSign']
# self['LangID'] = order_head['LangID']
# self['ItemNo'] = '' # Required, item to create row for
# self['c_Qty'] = None # Only one of qty or qtyaltenh may be used
# self['QtyAltEnh'] = None
# self['RequestedDate'] = '' # unused
# self['BatchId'] = order_head['BatchId']
# self['ArtSerieNr'] = '' # unused
# self['c_OrderType'] = None
# self['Run_Type'] = None # Could be 'R', but doesn't work
# self['c_TemplateRowID'] = None # No template used
# self['Edit'] = None # Custom order row text
# self['EditExt'] = None # Custom extended order row text
# self['Lagstalle'] = None # str: use default
# self['AltEnhetKod'] = '' # Override default alternative unit if desired
# self['AllocateAvailable'] = 0 # unused
# self['OverrideCreditLimit'] = 0 # Set to a char to override credit limit
# self['o_OrderRow'] = pymssql.output(int)
# self['o_NextQty'] = pymssql.output(float)
# self['o_NextDate'] = pymssql.output(str)
# self['o_LastQty'] = pymssql.output(float)
# self['o_LastDate'] = pymssql.output(str)
# self['o_AllocatedQty'] = pymssql.output(float)
# self['o_AllocatedDate'] = pymssql.output(str)
def callproc(self):
try:
super(OrderRow, self).callproc()
except pymssql.DatabaseError:
logger.error("A DatabaseException has been caught. Order row not created.")
return self['o_OrderRow']
class PlaceOrder(StoredProcedure):
"""Mapping for the Jeeves_Esales_PlaceOrder stored procedure parameters
webapp031 and WEBAPP003 determines default order status"""
def __init__(self, company_no, order_no, web_user_name=None, payment_method='card', data={}):
super(PlaceOrder, self).__init__('Jeeves_Esales_PlaceOrder')
self['c_CompanyNo'] = str(company_no)
self['c_OrderNumber'] = int(order_no)
self['c_kundref2'] = data.get('CustomerContact') # Er ref, kontaktperson
self['c_kundbestnr'] = data.get('CustomerReference')
self['c_editext'] = data.get('ExtraText') # Extern text
self['c_CoName'] = data.get('AddrName')
self['c_Addr1'] = data.get('AddrCO') # Lev.adress, c/o
self['c_Addr2'] = data.get('AddrStreet')
self['c_PostalCode'] = data.get('AddrPostalCode')
self['c_City'] = data.get('AddrCity')
self['c_CountryCode'] = data.get('AddrCountry', 'SE') # Ex: SE, FI etc.
self['c_godsmarke1'] = data.get('ShippingInfo')
self['c_godsmarke2'] = data.get('InternalInfo') # Kundspecifikt
notify_info = NotifyInfo(company_no).callproc()
self['c_TA_MailNotified'] = data.get('ShippingEmail', notify_info.get('email'))
self['c_TA_PhonNotifiedNo'] = data.get('ShippingPhone', notify_info.get('phone'))
self['c_TA_SMSNotifiedNo'] = data.get('ShippingSMS', notify_info.get('sms'))
# 1 = card, else invoice. Card requires manual update.
self['c_PaymentType'] = '1' if payment_method is 'card' else '0'
# Unique ID added to 'kpw' when invoicing is allowed.
self['c_webUserName'] = web_user_name
self['c_LevSattKod'] = 2 # 2 = Schenker, 4 = Collect
self['c_orderStatus'] = None # Override orderStatusCode when using invoicing
self['c_ForetagKod'] = 1 # Hardcoded to LK
self['c_orderStatus'] = None
self['c_ProvinceCode'] = None # For US customers etc.
def callproc(self):
try:
return super(PlaceOrder, self).callproc()
except pymssql.DatabaseError:
logger.error("A DatabaseException has been caught. Order %d not updated." %
(self['c_OrderNumber']))
class NotifyInfo(StoredProcedure):
"""Mapping for the JAPP_spr_LogTrade_Get_NotifyInfo stored procedure parameters
webapp031 and WEBAPP003 determines default order status"""
def __init__(self, company_no):
super(NotifyInfo, self).__init__('JAPP_spr_LogTrade_Get_NotifyInfo')
self['c_FtgNr'] = str(company_no)
self['c_ForetagKod'] = 1 # Hardcoded to LK
def callproc(self):
ret = {'email': None, 'sms': None, 'phone': None}
try:
result = super(NotifyInfo, self).callproc(resultset=True)
except pymssql.DatabaseError:
logger.error("A DatabaseException has been caught. NotifyInfo not fetched.")
return ret
if isinstance(result, list):
for r in result:
if r[1][7:].lower() in ret:
ret[r[1][7:].lower()] = r[0]
return ret

View file

@ -7,7 +7,7 @@
""" """
from models import Articles, Customers, InvoiceRows, OrderRows from models.meta import Articles, Customers, InvoiceRows, OrderRows
from sqlalchemy import desc from sqlalchemy import desc
from sqlalchemy.inspection import inspect from sqlalchemy.inspection import inspect
@ -19,9 +19,10 @@ class Process():
"""docstring for Process""" """docstring for Process"""
def __init__(self, jvs_query, db_session): def __init__(self, jvs_query, db_session):
super(Process, self).__init__() super(Process, self).__init__()
# Refactor code to use RawSession instead of raw jvs queries
self.query = jvs_query self.query = jvs_query
self.session = db_session self.session = db_session
self.logger = logging.getLogger("PyJeeves.process") self.logger = logging.getLogger("PyJeeves." + __name__)
def _update_model(self, model, kwargs): def _update_model(self, model, kwargs):
for k, v in kwargs.items(): for k, v in kwargs.items():
@ -47,6 +48,7 @@ class Process():
if _data: if _data:
self.logger.info("Syncing %s" % jvs_tbl) self.logger.info("Syncing %s" % jvs_tbl)
nth_item = 0
for item in _data: for item in _data:
_filter_kwargs = {k: item.get(k) for k in _p_keys} _filter_kwargs = {k: item.get(k) for k in _p_keys}
_existing = self.session.query(model).\ _existing = self.session.query(model).\
@ -57,6 +59,12 @@ class Process():
else: else:
_new = model(**item) _new = model(**item)
self.session.add(_new) self.session.add(_new)
if nth_item % 1000 == 0 and nth_item != 0:
self.session.commit()
nth_item += 1
self.session.commit()
else: else:
self.logger.info("No sync made for %s" % jvs_tbl) self.logger.info("No sync made for %s" % jvs_tbl)
@ -65,4 +73,3 @@ class Process():
self._sync_model(Articles, 'Articles') self._sync_model(Articles, 'Articles')
self._sync_model(InvoiceRows, 'InvoiceRows') self._sync_model(InvoiceRows, 'InvoiceRows')
self._sync_model(OrderRows, 'OrderRows') self._sync_model(OrderRows, 'OrderRows')
self.session.commit()

View file

@ -0,0 +1,6 @@
from .location import Location
from .article import Article, ArticleCategory
from .company import Company
from .pricelist import PriceList
from .order import Order
from .warehouse import Warehouse

View file

@ -0,0 +1,469 @@
# -*- coding: utf-8 -*-
from pyjeeves.models.raw import (
Article as ArticleModel,
ProductClass, ArticleClass, CommodityGroup, ArticleEAN, ArticleUnit)
from pyjeeves.models import db
from sqlalchemy.sql.expression import and_
from sqlalchemy.orm.exc import NoResultFound
from gtin import GTIN
from pyjeeves import logging
logger = logging.getLogger("PyJeeves." + __name__)
# Relocate Jeeves modules to separate folder and let a "master" module handle imports, and setup.
class Article():
"""Handles articles in Jeeves, currently filters out all articles with class = 2"""
@staticmethod
def get(art_no):
""" Query an article by number """
try:
return db.raw_session.query(ArticleModel).filter_by(
ArtNr=str(art_no)
).one()
except NoResultFound:
raise KeyError
@staticmethod
def get_all(filter_=and_(
ArticleModel.ItemStatusCode == 0,
ArticleModel.ArtKod != 2,
ArticleModel.VaruGruppKod != 90,
ArticleModel.ArtProdKlass != 0)
):
# .filter_by(ItemStatusCode=0, ArtKod=2)
return db.raw_session.query(ArticleModel).filter(filter_).all()
@staticmethod
def get_article_units(filter_=and_()):
return db.raw_session.query(ArticleUnit).filter(filter_).all()
@staticmethod
def is_salable(art_no_list=[]):
""" Returns true if all articles are salable,
else false with error information """
articles = db.raw_session.query(ArticleModel).filter(
and_(ArticleModel.ArtNr.in_(art_no_list))).all()
blocked_articles = [article.ArtNr for article in articles
if article.ArtKod == 2 or article.ItemStatusCode != 0]
unknown_articles = [x for x in art_no_list
if x not in set([article.ArtNr for article in articles])]
if blocked_articles or unknown_articles:
errors = {}
if blocked_articles:
errors['blocked_articles'] = blocked_articles
if unknown_articles:
errors['unknown_articles'] = unknown_articles
return False, errors
return True, {}
@staticmethod
def get_article_gtins():
return db.raw_session.query(ArticleEAN).all()
@staticmethod
def add_article_gtins(gtins=[], dry_run=False):
# Expects a list of dicts like this:
# [{
# 'article_no': article.ArtNr,
# 'article_gtin': gtin,
# 'unit': unit.AltEnhetKod,
# }]
for gtin in gtins:
n1 = ArticleEAN(
ArtNr=gtin['article_no'], AltEnhetKod=gtin.get('unit', None),
ArtNrEAN=str(gtin['article_gtin']), ForetagKod=1)
if dry_run:
logger.info('Creating GTIN for %s, %s, %s' % (n1.ArtNr, n1.AltEnhetKod, n1.ArtNrEAN))
continue
db.raw_session.add(n1)
logger.debug('Created/updated Article EAN for %s - %s with GTIN %s' % (
gtin['article_no'], gtin.get('unit', 'no unit'), gtin['article_gtin']))
db.raw_session.commit()
logger.info('Succesfully commited %s GTINs to database' % (len(gtins)))
@staticmethod
def clear_article_gtins():
gtins = db.raw_session.query(ArticleEAN).all()
for gtin in gtins:
db.raw_session.delete(gtin)
db.raw_session.commit()
logger.info('Deleted %s GTINs' % (len(gtins)))
class ArticleCategory():
"""Handles article categories, such as classes and groups in Jeeves"""
@staticmethod
def get_all():
# .filter_by(ItemStatusCode=0, ArtKod=2)
prod_classes = db.raw_session.query(ProductClass).all()
art_classes = db.raw_session.query(ArticleClass).all()
com_groups = db.raw_session.query(CommodityGroup).all()
return {'ProductClasses': prod_classes,
'ArticleClasses': art_classes, 'CommodityGroups': com_groups}
# TODO: Should be moved to separate project with Lindvalls specific code
def get_gtin_for_article(article_ean, article_unit=None, use_prefix=True):
# If we don't want to prefix with 0, then exclude them here.
UNIT_MAPPING = {
'Påse': '',
'st': '',
'paket': 0,
'200g': 0,
'kg': 9,
'Kart': 1,
'Bricka': 1,
'½-pall': 2,
'½-pall A': 2,
'tray_no_wrap': 8
}
prefixes = []
if article_unit:
# Find matching values in unit mapping
prefixes = [
val for key, val in UNIT_MAPPING.items()
if article_unit[0:len(key)].lower() in key.lower()]
if len(prefixes) > 1:
logger.warning('More than one unit match found in unit mapping')
# Use the first match
raw_gtin = (str(prefixes[0]) + article_ean) if prefixes and use_prefix else article_ean
# Handle GS1-128 GTIN code
if len(raw_gtin) >= 15 and raw_gtin[0:2] == '01':
raw_gtin = raw_gtin[2::]
article_gtin = GTIN(raw=raw_gtin)
return article_gtin
# TODO: Should be moved to separate project with Lindvalls specific code
def create_gtins_for_trading_goods(filename='gtin_trading_goods.csv'):
articles = Article.get_all(and_(
ArticleModel.ArtProdKlass == 4))
gtins = []
gtin_data = {}
import csv
with open(filename, newline='') as csvfile:
gtinreader = csv.reader(csvfile, delimiter=',')
headers = gtinreader.__next__()
logger.info('Found these columns: %s' % (', '.join(headers)))
for row in gtinreader:
gtin_data[row[0]] = row
logger.info("Found %s articles and updating with %s rows of data" % (
len(articles), len(gtin_data)))
for article in articles:
data = gtin_data.get(article.ArtNr)
if data:
default_set = False
if len(article.ArticleUnit) == 0 and data[3]:
logger.warning('Article %s has no ArticleUnits, but requires it' % (article.ArtNr))
for unit in article.ArticleUnit:
if unit.AltEnhetKod[0:3] == 'kart' and not data[3]:
logger.warning('Article %s missing kart unit' % (article.ArtNr))
if data[3] and unit.AltEnhetKod != 'st':
gtin = get_gtin_for_article(data[3], unit.AltEnhetKod, False)
# Only add GTINs for order units (not PO units)
if unit.AltEnhetOrder == '1':
gtins.append({
'article_no': article.ArtNr,
'article_gtin': gtin,
'unit': unit.AltEnhetKod
})
if unit.AltEnhetKod == 'st':
gtin = get_gtin_for_article(data[2], unit.AltEnhetKod, False)
# Only add GTINs for order units (not PO units)
if unit.AltEnhetOrder == '1':
gtins.append({
'article_no': article.ArtNr,
'article_gtin': gtin,
'unit': unit.AltEnhetKod
})
default_set = True
# Add default gtin if 'st' not used
if not default_set:
gtin = get_gtin_for_article(data[2], None, False)
gtins.append({
'article_no': article.ArtNr,
'article_gtin': gtin
})
else:
# Warn about active and stock items that didn't get updated.
if article.LagTyp == 0 and article.ItemStatusCode == 0:
logger.warning('Article %s has no GTIN data in CSV' % (article.ArtNr))
Article.add_article_gtins(gtins)
# TODO: Should be moved to separate project with Lindvalls specific code
def create_gtins(dry_run=True):
# GS1 Company Prefixes that we manage locally, prefixing etc.
LOCAL_GCPS = [
'731083', # Lindvalls Kaffe
'7392736', # Sackeus AB
'735007318', # Sarria Import AB
'732157', # Martin & Servera AB
'350096', # Scænsei Thee Kompani AB (Used by REKYL In Omnia Paratus AB)
'735003307', # Coffee Please
'735003711', # Emmas Skafferi AB (Used by Coffee Please)
'735003712', # Prefix no longer subscribed (Used by Coffee Please)
'735003302', # Josephine Selander - YogaGo (Used by Coffee Please)
]
articles = Article.get_all(and_(
ArticleModel.ItemStatusCode == 0,
ArticleModel.VaruGruppKod != 90,
ArticleModel.ArtProdKlass != 0))
articles_with_existing_gtins = [
gtin.ArtNr for i, gtin in enumerate(Article.get_article_gtins())]
gtins = []
for article in articles:
if article.ArtNr in articles_with_existing_gtins:
continue
if not article.ArtStreckKod:
logger.warning('No base GTIN for article %s' % (article.ArtNr))
continue
GCP = GTIN(raw=article.ArtStreckKod).gcp
if 12 < len(article.ArtStreckKod) < 12 and GCP in LOCAL_GCPS:
logger.error('Base GTIN is wrong length for article %s' % (article.ArtNr))
continue
# If GTIN is provided by vendor, skip prefixes and gohead if only one or no units exist.
if GCP not in LOCAL_GCPS and len(article.ArticleUnit) <= 1:
use_prefix = False
logger.info('Externally provided GTIN for %s, skipping prefixes' % (article.ArtNr))
elif GCP not in LOCAL_GCPS and len(article.ArticleUnit) > 1:
logger.warning('Externally provided GTIN for %s, too many units' % (article.ArtNr))
continue
else:
use_prefix = True
# Create gtin without ArticleUnit, for the base unit.
# gtins.append({
# 'article_no': article.ArtNr,
# 'article_gtin': get_gtin_for_article(article.ArtStreckKod, None, False)
# })
for unit in article.ArticleUnit:
# Skip paket for 21%, should only match HV with plastic wrapping.
if article.ArtNr[0:2] == '21' and unit.AltEnhetKod[0:5].lower() == 'paket':
logger.info('Skip paket unit for %s' % (article.ArtNr))
continue
# Special for 20%/30%, should only match HV without plastic wrapping.
if article.ArtNr[0:2] in ('20', '30') and unit.AltEnhetKod[0:6].lower() == 'bricka':
unit_code = 'tray_no_wrap'
else:
unit_code = unit.AltEnhetKod
gtin = get_gtin_for_article(article.ArtStreckKod, unit_code, use_prefix)
# Only add GTINs for order units (not PO units)
if unit.AltEnhetOrder == '1':
gtins.append({
'article_no': article.ArtNr,
'article_gtin': gtin,
'unit': unit.AltEnhetKod
})
# Workaround for scanning HV base units without plastic wrapping
if str(gtin)[0] == '0':
# Create gtin without ArticleUnit, for the base unit.
gtins.append({
'article_no': article.ArtNr,
'article_gtin': get_gtin_for_article(article.ArtStreckKod, None, False)
})
# Add GTIN to articles that don't use article units
# Should this still be added to arean/ArticleEAN???
# if len(article.ArticleUnit) == 0:
# gtin = get_gtin_for_article(article.ArtStreckKod, None, use_prefix)
# gtins.append({
# 'article_no': article.ArtNr,
# 'article_gtin': gtin,
# 'unit': None,
# })
# add_gtin_for_article(
# article.ArtNr, article.ArtStreckKod, None, use_prefix)
Article.add_article_gtins(gtins, dry_run)
# TODO: Should be moved to separate project with Lindvalls specific code
def find_articles_without_base_gtin():
articles = Article.get_all(and_(
ArticleModel.ItemStatusCode == 0,
ArticleModel.VaruGruppKod != 90,
ArticleModel.ArtProdKlass != 0))
_list = []
for article in articles:
if not article.ArtStreckKod:
_list.append(
{'artnr': article.ArtNr,
'artbeskr': article.ArtBeskr,
'error': 'no_base'})
continue
else:
if 12 < len(article.ArtStreckKod) < 12:
_list.append(
{'artnr': article.ArtNr,
'artbeskr': article.ArtBeskr,
'error': 'wrong_length'})
continue
for item in _list:
print('{artnr}, "{artbeskr}", {error}'.format(**item))
# TODO: Should be moved to separate project with Lindvalls specific code
def set_storage_type():
articles = Article.get_all(and_(
ArticleModel.LagTyp == 0,
ArticleModel.ItemStatusCode == 0,
ArticleModel.AnskaffningsSatt == 10))
for article in articles:
article.LagTyp = 4
db.raw_session.commit()
logger.info("Updated storage type for %s articles" % (len(articles)))
# TODO: Should be moved to separate project with Lindvalls specific code
def set_zone_placement():
# Logic for article groups and zones
# ArticleClass descides which zone to put it.
# set Article.ArticleBalance[0].japp_ewms_rec_zoneid to correct zoneid
article_class_map = {
'Kaffe': 'U',
'OoH-Kaffe': 'K',
'Private Label': 'S',
'Tillbehör och maskiner': 'U',
'Komplement': 'U'
}
articles = Article.get_all(and_(
ArticleModel.ItemStatusCode == 0,
ArticleModel.AnskaffningsSatt == 10))
zone_placements_update = 0
for article in articles:
zone_id = article_class_map.get(article.ArticleClass.ArtTypBeskr)
if zone_id and article.ArticleBalance:
article.ArticleBalance[0].JAPP_EWMS_REC_ZoneID = zone_id
zone_placements_update += 1
else:
logger.info("Excluded %s, wrong article class or no balance " % (article.ArtNr))
db.raw_session.commit()
logger.info("Updated placement zone for %s articles" % (zone_placements_update))
# a = Article.get('2109')
# print([ab.to_dict() for ab in a['ArticleBalance']])
def update_decimals_on_alt_units():
units = Article.get_article_units(ArticleUnit.AltEnhetKod == 'påse')
updated_units = 0
for unit in units:
if unit.AltEnhetOmrFaktor is not None:
dec_count = 0
for digit in unit.AltEnhetOmrFaktor.as_tuple().digits:
if digit != 0:
dec_count += 1
unit.AltEnhetAntDec = dec_count
updated_units += 1
db.raw_session.commit()
logger.info("Updated decimal count for %s article units" % (updated_units))
if __name__ == '__main__':
# print([column.key for column in Company.__table__.columns])
# from pprint import pprint
# logger.info("Starting TEST")
# session = RawSession()
# logger.info("Testing gettings an article")
# # c1 = session.query(Company).filter_by(FtgNr="179580").first()
# # print(ArticleModel)
# c1 = db.raw_session.query(ArticleModel).filter_by(ArtNr="2003").first()
# c1 = Article.get("2003")
# pprint([unit.to_dict() for unit in c1.ArticleUnit])
# pprint(c1.to_dict())
# pprint([(au.to_dict(), au.AltEnhetOrder) for au in c1.ArticleUnit])
# logger.info(c1.to_dict())
# print(
# len(Article.get_all())
# )
# c1 = db.raw_session.query(ArticleEAN).all()
# pprint([c.to_dict() for c in c1])
# c1 = db.raw_session.query(ArticleEAN).filter_by(ArtNr="1054").first()
# pprint(c1.to_dict())
# c1.ArtNrEAN = '7310830010548'
# pprint(c1.to_dict())
# c1.save()
# logger.info(c1.to_dict())
# create_gtins_for_trading_goods('gtin_trading_goods_test.csv')
# LIVE FUNCTIONS BELOW
# find_articles_without_base_gtin()
# logger.info("Truncating GTINs")
# Article.clear_article_gtins()
logger.info("Creating new GTINs from base GTIN")
create_gtins(dry_run=False)
# logger.info("Creating new GTINs from trading goods CSV")
# create_gtins_for_trading_goods()
# logger.info("Update articles for batch management")
# set_storage_type()
# logger.info("Set zone information on article balance")
# set_zone_placement()
# logger.info("Updating alt units")
# update_decimals_on_alt_units()

View file

@ -0,0 +1,129 @@
# -*- coding: utf-8 -*-
from pyjeeves.models.raw import Company as CompanyModel, Customer as CustomerModel
from pyjeeves.models import db
from sqlalchemy.sql.expression import and_
from sqlalchemy.orm.strategy_options import Load
from sqlalchemy.orm.exc import NoResultFound
from pyjeeves import logging
logger = logging.getLogger("PyJeeves." + __name__)
# Relocate Jeeves modules to separate folder and let a "master" module handle imports, and setup.
class Company():
"""Handles companies in Jeeves"""
@staticmethod
def get(ftg_nr):
""" Query an article by number """
try:
return db.raw_session.query(CompanyModel).filter_by(
FtgNr=ftg_nr
).one()
except NoResultFound:
raise KeyError
@staticmethod
def get_all_active_customers():
cust = db.raw_session.query(CustomerModel).filter(and_(CustomerModel.Makulerad == 0)).all()
return [c.CompanyModel for c in cust]
@staticmethod
def get_customers(category_list=[10], class_list=[], filter_inactive=True, filters=and_()):
category_in = CustomerModel.kundkategorikod.in_(category_list) if category_list else and_()
class_in = CustomerModel.kundklass.in_(class_list) if class_list else and_()
inactive = and_(CustomerModel.Makulerad == 0) if filter_inactive else and_()
return db.raw_session.query(CustomerModel).options(
Load(CompanyModel).noload('*')).filter(
and_(category_in, class_in, inactive, filters)).all()
@staticmethod
def get_customer_numbers(category_list=[10], class_list=[], filter_inactive=True):
cust = Company.get_customers(category_list, class_list, filter_inactive)
return [c.FtgNr for c in cust]
@staticmethod
def get_list(ftg_nr=[], filter_=and_(CustomerModel.Makulerad == 0), offset=0, limit=100):
ftg_filter = and_()
if ftg_nr:
ftg_filter = CompanyModel.FtgNr.in_(ftg_nr)
return db.raw_session.query(CompanyModel).join(CustomerModel).filter(
and_(ftg_filter, filter_)).order_by(
CompanyModel.FtgNr.desc()).offset(offset).limit(limit).all()
# TODO: Should be moved to separate project with Lindvalls specific code
def update_customer_delivery_from_csv(filename='zip_codes_svhl.csv'):
SVHL_ZONES = {}
logger.info("Get customers")
customers = Company.get_customers(filters=(
and_(CustomerModel.LevSattKod == 3, CustomerModel.kundklass == None))) # noqa
logger.info("Amount of customers is %d" % len(customers))
import csv
with open(filename, newline='') as csvfile:
shelfreader = csv.reader(csvfile, delimiter=',')
headers = shelfreader.__next__()
logger.info('Found these columns: %s' % (', '.join(headers)))
for row in shelfreader:
SVHL_ZONES[row[0]] = '%s - %s' % (row[2], row[1])
logger.info('Length of zones dict is %d' % (len(SVHL_ZONES)))
customers_to_update = 0
for customer in customers:
if customer.Company.FtgLevPostNr:
FtgLevPostNr = customer.Company.FtgLevPostNr.strip().replace(" ", "")
if FtgLevPostNr and FtgLevPostNr in SVHL_ZONES:
logger.info('FtgLevPostNr: %s - %s is within SVHL zone %s' % (
customer.FtgNr, customer.Company.FtgNamn, SVHL_ZONES[FtgLevPostNr]))
customers_to_update += 1
customer.LevSattKod = 11
continue
# Return? Break?
if customer.Company.FtgPostnr:
FtgPostnr = customer.Company.FtgPostnr.strip().replace(" ", "")
if FtgPostnr and FtgPostnr in SVHL_ZONES:
logger.info('FtgPostnr: %s - %s is within SVHL zone %s' % (
customer.FtgNr, customer.Company.FtgNamn, SVHL_ZONES[FtgPostnr]))
customer.LevSattKod = 11
customers_to_update += 1
logger.info('Amount updated %d' % customers_to_update)
# db.raw_db.merge(n1)
db.raw_session.commit()
logger.info('Succesfully commited updated customers to database')
if __name__ == '__main__':
# print([column.key for column in CompanyModel.__table__.columns])
# logger.info("Starting TEST")
# session = RawSession()
# logger.info("Testing gettings a company")
# c1 = session.query(CompanyModel).filter_by(FtgNr="179580").first()
# print(CompanyModel)
# print(CompanyModel.get_list(['406569', '179580', '2440070', '179584']))
# from pprint import pprint
# pprint(CompanyModel.get('179584').to_dict())
# c1 = CompanyModel.query.filter_by(FtgNr="406569").first()
# print(c1)
# logger.info(c1.json)
# print(
# len(CompanyModel.get_all_active_customers())
# )
# print(CompanyModel.get_all_active_customers()[0].CompanyModel)
logger.info("Starting")
update_customer_delivery_from_csv()

View file

@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
from pyjeeves.models.raw import Company, DelivLoc
from pyjeeves import logging
logger = logging.getLogger("PyJeeves." + __name__)
# Relocate Jeeves modules to separate folder and let a "master" module handle imports, and setup.
class Location():
"""Handles dispatch locations in Jeeves"""
def __init__(self):
super(Location, self).__init__()
self.associated_company = '' # Company with new/existing locations
self._deliv_locs = [] # List of locations to be connected
def _connect_deliv_loc(self, ftgnr, description, code):
if self.associated_company == '':
raise
if len(description) > 36:
logger.warn("Truncated description %s", (description))
description = description[:36]
_deliv_loc = DelivLoc(
FtgNr=self.associated_company, OrdLevPlats1=ftgnr,
OrdLevPlBeskr=description, ForetagKod=1)
self._deliv_locs.append(_deliv_loc)
# self.session.merge(_deliv_loc)
return _deliv_loc
def create_lev_location(self, ftgnr='', name='', address='',
postal_code='', city='', gln='', invoice_ref='', phone=''):
_loc = Company(
FtgNr=str(ftgnr), FtgNamn=name, FtgPostadr5=address,
FtgLevPostNr=postal_code, FtgPostLevAdr3=city,
EAN_Loc_Code=gln, FtgPostAdr1=invoice_ref, ComNr=phone,
ForetagKod=1)
# logger.debug("Adding company to location session")
# with self.session.no_autoflush:
# # self.session.merge(_loc) # "merge" updates if existing location exists.
_deliv_loc = self._connect_deliv_loc(ftgnr, name, gln)
return _loc, _deliv_loc
def save_locations(self):
logger.debug("Committing all location changes")
# self.session.commit() # Location company needs to be created in order to connect them.
for deliv_loc in self._deliv_locs:
deliv_loc.merge()
# self.session.merge(deliv_loc) # Create "connnections" between Customer and Location.
Company.commit()
# self.session.commit()
if __name__ == '__main__':
# print([column.key for column in Company.__table__.columns])
logger.info("Starting TEST")
# session = RawSession()
logger.info("Testing gettings a company")
# c1 = session.query(Company).filter_by(FtgNr="179580").first()
print(Company)
c1 = Company.query.filter_by(FtgNr="179580").first()
logger.info(c1.json)
# RawSession.remove()
# from sqlalchemy.inspection import inspect
# print (inspect(Company).columns.items())

View file

@ -0,0 +1,173 @@
# -*- coding: utf-8 -*-
from pyjeeves.models.raw import Order as OrderModel, OrderItem as OrderItemModel
from pyjeeves.models import db
from sqlalchemy.sql.expression import and_
from sqlalchemy.orm.exc import NoResultFound
from pyjeeves import logging
logger = logging.getLogger("PyJeeves." + __name__)
# Relocate Jeeves modules to separate folder and let a "master" module handle imports, and setup.
class Order():
"""Handles orders in Jeeves"""
@staticmethod
def get(order_no):
""" Query an order by number """
try:
return db.raw.query(OrderModel).filter_by(
OrderNr=order_no
).one()
except NoResultFound:
raise KeyError
@staticmethod
def get_all_unregistered_order():
order = OrderModel.query.filter(and_(OrderModel.OrderStatusCode == 00)).all()
return order
@staticmethod
def get_list(order_no=[]):
return db.raw.query(OrderModel).filter(
OrderModel.OrderNr.in_(order_no)
).all()
@staticmethod
def get_all_by_company(ftg_nr=None):
if not ftg_nr:
raise KeyError
return db.raw.query(OrderModel).filter(
OrderModel.FtgNr == str(ftg_nr)
).all()
@staticmethod
def create(customer_no, head={}, items=[], web_user_name=None, pers_sign='biz'):
head['CompanyNumber'] = str(customer_no)
head['PersSign'] = str(pers_sign)
# Create order from head dict to get an order number
order, invoice_possible = OrderModel(head).create(web_user_name)
if not order['OrderNr']:
raise
# Go through order items, if any, and save them to DB.
order['OrderItems'] = Order.create_rows(order, items)
# Save the information in the order object
# Boolean argument deceides if order has contact person, and should be set 'registered'
order.save(invoice_possible, web_user_name)
return order
@staticmethod
def create_rows(order, items=[], pers_sign='biz'):
rv = []
for item in items:
if not isinstance(item, OrderItemModel):
item = OrderItemModel(item)
# Set RequestedDeliveryDate from order head if not specified for items.
if order['OrdBerLevDat'] and not item['OrdBegLevDat']:
item['OrdBegLevDat'] = order['OrdBerLevDat']
item['OrderNr'] = order['OrderNr']
item['FtgNr'] = order['FtgNr']
item['PersSign'] = pers_sign
rv.append(item.save())
return rv
if __name__ == '__main__':
logger.info("Starting TEST")
logger.info("Testing getting an order")
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
# import os
# os.environ['TDSDUMP'] = 'stdout'
# Jeeves_Esales_CreateOrder
# All "Jeeves_Esales_" procedures may perhaps be used?
# select *
# from LKTest.information_schema.routines
# where routine_type = 'PROCEDURE'
# AND SPECIFIC_NAME LIKE '%Jeeves_Esales_%';
# data = {
# 'OrderNr': 500500,
# 'OrdDatum': datetime.now(),
# 'OrdTyp': 1,
# 'FtgNr': customer_no,
# 'OrdBerLevDat': datetime(2019, 7, 10),
# 'ValKod': 'SEK',
# 'OrderItems': [
# {'ArtNr': '2005',
# 'OrdAntal': 5.4}
# ],
# 'Saljare': '500',
# 'PersSign': 'marlin',
# 'MomsKod': 2,
# 'BetKod': '10',
# 'LevVillkKod': 3,
# 'LevSattKod': 2,
# 'LagStalle': 0,
# 'ForetagKod': 1
# }
order_head = {
'AddrCO': '',
'AddrCity': 'Uppsala',
'AddrCountry': 'SE',
'AddrName': 'Lindvalls Kaffe',
'AddrPostalCode': '751 82',
'AddrStreet': 'Kungsgatan 60',
'CompanyName': 'Lindvalls Kaffe AB (övrigt)',
'CompanyNumber': '179580',
'CustomerContact': 'Test beställning',
'CustomerReference': 'no po number',
'InternalInfo': 'Test order',
'OrderNumber': 419040,
'OrderStatusCode': 13,
'ShippingEmail': 'order@lindvallskaffe.se',
'ShippingInfo': 'Lev till godsmottagning',
'ShippingPhone': '018-480 20 00',
'ShippingSMS': '0703 25 25 02',
'ShippingTypeCode': 4}
order_items = [
{'AltEnhetKod': 'Bricka5,4',
'ArticleName': 'Lindvalls Mellanrost',
'ArticleNumber': '2003',
'OrdAntalAltEnh': '1.00',
'OrderRowNumber': 10,
'UnitAmount': '5.40',
'UnitPrice': '92.00'},
{'AltEnhetKod': 'Bricka5,4',
'ArticleName': 'Lindvalls Mellanrost',
'ArticleNumber': '2003',
'OrdAntalAltEnh': 1.666666667,
'OrderRowNumber': 20,
'UnitAmount': '9.00',
'UnitPrice': '92.00'},
{'AltEnhetKod': 'Bricka5,4',
'ArticleName': 'Lindvalls Mellanrost',
'ArticleNumber': '2003',
'OrdAntalAltEnh': '5.00',
'OrderRowNumber': 30,
'UnitAmount': '27.00',
'UnitPrice': '92.00'}]
# print(Order.get_list(['406569', '179580', '2440070', '179584']))
from pprint import pprint
# pprint(Order.get(7000028).to_dict())
pprint(Order.create('179584', order_head, order_items).to_dict())
# pprint(Order.get('419033').to_dict())
# c1 = CompanyModel.query.filter_by(FtgNr="406569").first()
# print(c1)
# logger.info(c1.json)

View file

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
from pyjeeves.models.raw import PriceList as PriceListModel
from pyjeeves.models import db
from sqlalchemy.orm.exc import NoResultFound
from pyjeeves import logging
logger = logging.getLogger("PyJeeves." + __name__)
# Relocate Jeeves modules to separate folder and let a "master" module handle imports, and setup.
class PriceList():
"""Handles price lists in Jeeves"""
@staticmethod
def get(price_list_no):
""" Query a price list by number """
try:
return db.raw.query(PriceListModel).filter_by(
PrisLista=price_list_no
).one()
except NoResultFound:
raise KeyError

View file

@ -0,0 +1,142 @@
from pyjeeves.models.raw import ArticleShelf, ItemReplenishmentLevels
from pyjeeves.models import db
from sqlalchemy.sql.expression import and_
from sqlalchemy.orm.exc import NoResultFound
from pyjeeves.repositories import Article
from pyjeeves import logging
logger = logging.getLogger("PyJeeves." + __name__)
# Relocate Jeeves modules to separate folder and let a "master" module handle imports, and setup.
class Warehouse():
"""Handles articles in Jeeves, currently filters out all articles with class = 2"""
@staticmethod
def get_shelf(shelf_id):
""" Query a shelf by id """
try:
return db.raw_session.query(ArticleShelf).filter_by(
LagPlats=str(shelf_id)
).one()
except NoResultFound:
raise KeyError
@staticmethod
def get_all_shelfs(filter_=and_()):
# .filter_by(ItemStatusCode=0, ArtKod=2)
return db.raw_session.query(ArticleShelf).filter(filter_).all()
@staticmethod
def delete_empty_shelfs():
shelfs = db.raw_session.query(ArticleShelf).filter(and_(
ArticleShelf.LagStalle == 0,
ArticleShelf.JAPP_EWMS_zoneid.in_(['U', 'K', 'S']),
ArticleShelf.LagSaldo == 0)).all()
for shelf in shelfs:
db.raw_session.delete(shelf)
db.raw_session.commit()
logger.info('Deleted %s shelfs' % (len(shelfs)))
@staticmethod
def delete_replenish():
replenish = db.raw_session.query(ItemReplenishmentLevels).all()
for repl in replenish:
db.raw_session.delete(repl)
db.raw_session.commit()
logger.info('Deleted old replenishment levels')
@staticmethod
def add_replenish(shelfs=[]):
Warehouse.delete_replenish()
min_level = 54 # Should be how low we'd like the level to get. One layer on pallet?
max_level = 270 # How high is the roof?
# with db.raw_session.no_autoflush:
for shelf in shelfs:
art = Article.get(shelf['article_no'])
default_alt_unit = [unit for unit in art.ArticleUnit if unit.AltEnhetOrderStd == '1']
if default_alt_unit and len(default_alt_unit) == 1:
if default_alt_unit[0].AltEnhetOmrFaktor is not None:
unit_multiple = default_alt_unit[0].AltEnhetOmrFaktor
else:
unit_multiple = default_alt_unit[0].ArticleAlternativeUnit.AltEnhetOmrFaktor
else:
unit_multiple = 1
new_level = ItemReplenishmentLevels(
LagStalle='0', LagPlats=shelf['shelf'], ArtNr=shelf['article_no'],
JAPP_EWMS_minnoofitemsonbin=min_level,
MaxNoOfItemsOnBin=max_level,
JAPP_EWMS_multipel=unit_multiple,
ForetagKod=1)
db.raw_session.add(new_level)
db.raw_session.commit()
# TODO: Should be moved to separate project with Lindvalls specific code
def update_shelfs_from_csv(filename='shelf_numbers_20211217.csv'):
SHELF_TYPES = {
'Hyllplats': 'HP',
'Pallplats': 'PP',
'Pallrad': 'PR'
}
SKIP_ZONES = ('U', 'K', 'KB', 'S', 'G')
import csv
with open(filename, newline='') as csvfile:
shelfreader = csv.reader(csvfile, delimiter=',')
headers = shelfreader.__next__()
logger.info('Found these columns: %s' % (', '.join(headers)))
for row in shelfreader:
if row[3] in SKIP_ZONES:
continue
maxkg = int(row[4]) if row[4] else 0
multiitems = '1' if row[2] == 'Pallrad' else '0'
n1 = ArticleShelf(
LagPlats=row[1], JAPP_EWMS_zoneid=row[3], LagStalle='0',
LagPlatsTyp=SHELF_TYPES.get(row[2]), MaxNoOfItemsOnBin=maxkg,
MultiItemsOnBin=multiitems, JAPP_EWMS_AllowMultipleBatchesOnBin=multiitems,
ForetagKod=1)
db.raw_session.merge(n1)
db.raw_session.commit()
logger.info('Succesfully commited shelfs to database')
# TODO: Should be moved to separate project with Lindvalls specific code
def update_replenishment_levels(filename='repl_shelfs.csv'):
import csv
repl_shelfs = []
with open(filename, newline='') as csvfile:
shelfreader = csv.reader(csvfile, delimiter=',')
headers = shelfreader.__next__()
logger.info('Found these columns: %s' % (', '.join(headers)))
for row in shelfreader:
repl_shelfs.append({
'shelf': row[0],
'article_no': str(row[1])
})
logger.info('Updating %d replenishment levels' % (len(repl_shelfs)))
Warehouse.add_replenish(repl_shelfs)
logger.info('Succesfully commited levels to database')
if __name__ == '__main__':
# from pprint import pprint
# all_shelfs = Warehouse.get_all_shelfs()
# pprint([shelf.to_dict() for shelf in all_shelfs])
# Warehouse.delete_empty_shelfs()
update_shelfs_from_csv()
# update_replenishment_levels()

View file

@ -1,7 +1,9 @@
nose nose>=1.3.7
sphinx Sphinx>=3.2.1
pymssql pymssql>=2.2.7
sqlalchemy SQLAlchemy>=1.3.22
PyMySQL sqlservice>=2.0.0
alembic PyMySQL>=0.10.0
pyyaml alembic>=1.4.2
PyYAML>=5.3.1
gtin>=0.1.13

View file

@ -3,7 +3,7 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
with open('README.rst') as f: with open('README.md') as f:
readme = f.read() readme = f.read()
with open('LICENSE') as f: with open('LICENSE') as f:
@ -12,12 +12,22 @@ with open('LICENSE') as f:
setup( setup(
name='pyjeeves', name='pyjeeves',
version='0.0.1', version='0.0.1',
description='PyJeeves syncronization module', description='PyJeeves communication module',
long_description=readme, long_description=readme,
author='Marcus Lindvall', author='Marcus Lindvall',
author_email='marcus.lindvall@lindvallskaffe.se', author_email='marcus.lindvall@lindvallskaffe.se',
url='https://gitlab.lndvll.se/lindvallskaffe/pyjeeves', url='https://gitlab.lndvll.se/lindvallskaffe/pyjeeves',
license=license, license=license,
packages=find_packages(exclude=('tests', 'docs', 'sample', 'env', 'migrations')) packages=find_packages(exclude=('tests', 'docs', 'sample', 'env', 'migrations')),
install_requires=[
'nose',
'sphinx',
'pymssql-py38',
'sqlalchemy',
'sqlservice',
'PyMySQL',
'alembic',
'pyyaml',
'gtin'
]
) )