feat(cardex): implement cardex controller, service, and DTO for stock movements
feat(pos): add POS controller and service for order creation and stock retrieval refactor(pos): enhance stock and product category retrieval with pagination and mapping
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^11.0.1",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/jwt": "^11.0.2",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/swagger": "^11.2.3",
|
||||
"@prisma/adapter-mariadb": "^7.1.0",
|
||||
@@ -12,6 +13,7 @@
|
||||
"dayjs": "^1.11.19",
|
||||
"dotenv": "^17.2.3",
|
||||
"jalaliday": "^3.1.1",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"mysql2": "^3.15.3",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1"
|
||||
|
||||
Generated
+111
@@ -14,6 +14,9 @@ importers:
|
||||
'@nestjs/core':
|
||||
specifier: ^11.0.1
|
||||
version: 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@nestjs/jwt':
|
||||
specifier: ^11.0.2
|
||||
version: 11.0.2(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))
|
||||
'@nestjs/platform-express':
|
||||
specifier: ^11.0.1
|
||||
version: 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)
|
||||
@@ -41,6 +44,9 @@ importers:
|
||||
jalaliday:
|
||||
specifier: ^3.1.1
|
||||
version: 3.1.1(dayjs@1.11.19)
|
||||
jsonwebtoken:
|
||||
specifier: ^9.0.3
|
||||
version: 9.0.3
|
||||
mysql2:
|
||||
specifier: ^3.15.3
|
||||
version: 3.15.3
|
||||
@@ -915,6 +921,11 @@ packages:
|
||||
'@nestjs/websockets':
|
||||
optional: true
|
||||
|
||||
'@nestjs/jwt@11.0.2':
|
||||
resolution: {integrity: sha512-rK8aE/3/Ma45gAWfCksAXUNbOoSOUudU0Kn3rT39htPF7wsYXtKfjALKeKKJbFrIWbLjsbqfXX5bIJNvgBugGA==}
|
||||
peerDependencies:
|
||||
'@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
|
||||
|
||||
'@nestjs/mapped-types@2.1.0':
|
||||
resolution: {integrity: sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==}
|
||||
peerDependencies:
|
||||
@@ -1144,9 +1155,15 @@ packages:
|
||||
'@types/json-schema@7.0.15':
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
'@types/jsonwebtoken@9.0.10':
|
||||
resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==}
|
||||
|
||||
'@types/methods@1.1.4':
|
||||
resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==}
|
||||
|
||||
'@types/ms@2.1.0':
|
||||
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
|
||||
|
||||
'@types/node@22.19.1':
|
||||
resolution: {integrity: sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==}
|
||||
|
||||
@@ -1575,6 +1592,9 @@ packages:
|
||||
bser@2.1.1:
|
||||
resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
|
||||
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
@@ -1854,6 +1874,9 @@ packages:
|
||||
eastasianwidth@0.2.0:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
|
||||
|
||||
ee-first@1.1.1:
|
||||
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
|
||||
|
||||
@@ -2555,6 +2578,16 @@ packages:
|
||||
jsonfile@6.2.0:
|
||||
resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
|
||||
|
||||
jsonwebtoken@9.0.3:
|
||||
resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==}
|
||||
engines: {node: '>=12', npm: '>=6'}
|
||||
|
||||
jwa@2.0.1:
|
||||
resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==}
|
||||
|
||||
jws@4.0.1:
|
||||
resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==}
|
||||
|
||||
keyv@4.5.4:
|
||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||
|
||||
@@ -2592,12 +2625,33 @@ packages:
|
||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
lodash.includes@4.3.0:
|
||||
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
|
||||
|
||||
lodash.isboolean@3.0.3:
|
||||
resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
|
||||
|
||||
lodash.isinteger@4.0.4:
|
||||
resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==}
|
||||
|
||||
lodash.isnumber@3.0.3:
|
||||
resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==}
|
||||
|
||||
lodash.isplainobject@4.0.6:
|
||||
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
|
||||
|
||||
lodash.isstring@4.0.1:
|
||||
resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
|
||||
|
||||
lodash.memoize@4.1.2:
|
||||
resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
|
||||
|
||||
lodash.merge@4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
|
||||
lodash.once@4.1.1:
|
||||
resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
|
||||
|
||||
lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
|
||||
@@ -4441,6 +4495,12 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@nestjs/platform-express': 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)
|
||||
|
||||
'@nestjs/jwt@11.0.2(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
'@types/jsonwebtoken': 9.0.10
|
||||
jsonwebtoken: 9.0.3
|
||||
|
||||
'@nestjs/mapped-types@2.1.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)':
|
||||
dependencies:
|
||||
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
|
||||
@@ -4711,8 +4771,15 @@ snapshots:
|
||||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
|
||||
'@types/jsonwebtoken@9.0.10':
|
||||
dependencies:
|
||||
'@types/ms': 2.1.0
|
||||
'@types/node': 22.19.1
|
||||
|
||||
'@types/methods@1.1.4': {}
|
||||
|
||||
'@types/ms@2.1.0': {}
|
||||
|
||||
'@types/node@22.19.1':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
@@ -5193,6 +5260,8 @@ snapshots:
|
||||
dependencies:
|
||||
node-int64: 0.4.0
|
||||
|
||||
buffer-equal-constant-time@1.0.1: {}
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
buffer@5.7.1:
|
||||
@@ -5427,6 +5496,10 @@ snapshots:
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
ee-first@1.1.1: {}
|
||||
|
||||
effect@3.18.4:
|
||||
@@ -6368,6 +6441,30 @@ snapshots:
|
||||
optionalDependencies:
|
||||
graceful-fs: 4.2.11
|
||||
|
||||
jsonwebtoken@9.0.3:
|
||||
dependencies:
|
||||
jws: 4.0.1
|
||||
lodash.includes: 4.3.0
|
||||
lodash.isboolean: 3.0.3
|
||||
lodash.isinteger: 4.0.4
|
||||
lodash.isnumber: 3.0.3
|
||||
lodash.isplainobject: 4.0.6
|
||||
lodash.isstring: 4.0.1
|
||||
lodash.once: 4.1.1
|
||||
ms: 2.1.3
|
||||
semver: 7.7.3
|
||||
|
||||
jwa@2.0.1:
|
||||
dependencies:
|
||||
buffer-equal-constant-time: 1.0.1
|
||||
ecdsa-sig-formatter: 1.0.11
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
jws@4.0.1:
|
||||
dependencies:
|
||||
jwa: 2.0.1
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
keyv@4.5.4:
|
||||
dependencies:
|
||||
json-buffer: 3.0.1
|
||||
@@ -6397,10 +6494,24 @@ snapshots:
|
||||
dependencies:
|
||||
p-locate: 5.0.0
|
||||
|
||||
lodash.includes@4.3.0: {}
|
||||
|
||||
lodash.isboolean@3.0.3: {}
|
||||
|
||||
lodash.isinteger@4.0.4: {}
|
||||
|
||||
lodash.isnumber@3.0.3: {}
|
||||
|
||||
lodash.isplainobject@4.0.6: {}
|
||||
|
||||
lodash.isstring@4.0.1: {}
|
||||
|
||||
lodash.memoize@4.1.2: {}
|
||||
|
||||
lodash.merge@4.6.2: {}
|
||||
|
||||
lodash.once@4.1.1: {}
|
||||
|
||||
lodash@4.17.21: {}
|
||||
|
||||
log-symbols@4.1.0:
|
||||
|
||||
@@ -15,17 +15,13 @@ CREATE INDEX `Sales_Invoices_inventoryId_fkey` ON `Sales_Invoices` (`inventoryId
|
||||
ALTER TABLE `Sales_Invoices`
|
||||
ADD CONSTRAINT `Sales_Invoices_inventoryId_fkey` FOREIGN KEY (`inventoryId`) REFERENCES `Inventories` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- ------------------------------------------
|
||||
-- Trigger: trg_transfer_item_after_insert
|
||||
-- Event: INSERT
|
||||
-- Table: Inventory_Transfer_Items
|
||||
-- ------------------------------------------
|
||||
DROP TRIGGER IF EXISTS `trg_transfer_item_after_insert`;
|
||||
|
||||
CREATE DEFINER=`pos`@`%` TRIGGER `trg_transfer_item_after_insert` AFTER INSERT ON `Inventory_Transfer_Items` FOR EACH ROW begin
|
||||
CREATE TRIGGER `trg_transfer_item_after_insert` AFTER
|
||||
INSERT
|
||||
ON `Inventory_Transfer_Items` FOR EACH ROW begin
|
||||
|
||||
|
||||
DECLARE fromInv INT;
|
||||
DECLARE fromInv INT;
|
||||
DECLARE toInv INT;
|
||||
DECLARE _avgCost DECIMAL(10,2);
|
||||
DECLARE latestQuantityInOrigin DECIMAL(10,2);
|
||||
@@ -61,9 +57,11 @@ end;
|
||||
-- ------------------------------------------
|
||||
DROP TRIGGER IF EXISTS `trg_purchase_receipt_item_after_insert`;
|
||||
|
||||
CREATE DEFINER=`pos`@`%` TRIGGER `trg_purchase_receipt_item_after_insert` AFTER INSERT ON `Purchase_Receipt_Items` FOR EACH ROW BEGIN
|
||||
DECLARE latestQuantity DECIMAL(10,2) DEFAULT 0;
|
||||
DECLARE invId INT;
|
||||
CREATE TRIGGER `trg_purchase_receipt_item_after_insert` AFTER
|
||||
INSERT
|
||||
ON `Purchase_Receipt_Items` FOR EACH ROW BEGIN DECLARE latestQuantity DECIMAL(10, 2) DEFAULT 0;
|
||||
|
||||
DECLARE invId INT;
|
||||
DECLARE suppId INT;
|
||||
|
||||
-- Get inventory & supplier from receipt
|
||||
@@ -107,11 +105,103 @@ CREATE DEFINER=`pos`@`%` TRIGGER `trg_purchase_receipt_item_after_insert` AFTER
|
||||
CASE
|
||||
WHEN NEW.count = 0 THEN 0
|
||||
ELSE NEW.total / NEW.count
|
||||
END,
|
||||
END
|
||||
|
||||
,
|
||||
suppId,
|
||||
latestQuantity + NEW.count,
|
||||
NOW()
|
||||
);
|
||||
|
||||
END;
|
||||
|
||||
-- ------------------------------------------
|
||||
-- Trigger: trg_sales_invoice_items_before_insert
|
||||
-- Event: INSERT
|
||||
-- Table: Sales_Invoice_Items
|
||||
-- ------------------------------------------
|
||||
DROP TRIGGER IF EXISTS `trg_sales_invoice_items_before_insert`;
|
||||
|
||||
CREATE TRIGGER `trg_sales_invoice_items_before_insert` BEFORE
|
||||
INSERT
|
||||
ON `Sales_Invoice_Items` FOR EACH ROW begin DECLARE current_stock DECIMAL(10, 2);
|
||||
|
||||
DECLARE inventory_id INT;
|
||||
|
||||
|
||||
SELECT inventoryId INTO inventory_id
|
||||
FROM Sales_Invoices si
|
||||
WHERE si.id = NEW.invoiceId
|
||||
LIMIT 1;
|
||||
|
||||
SELECT COALESCE(quantity, 0) INTO current_stock
|
||||
FROM Stock_Balance sb
|
||||
WHERE productId = NEW.productId AND sb.inventoryId = inventory_id
|
||||
LIMIT 1;
|
||||
|
||||
|
||||
|
||||
IF NEW.count > current_stock THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = 'موجودی کالا در انبار کافی نیست.';
|
||||
END IF;
|
||||
end;
|
||||
|
||||
-- ------------------------------------------
|
||||
-- Trigger: trg_sales_invoice_items_after_insert
|
||||
-- Event: INSERT
|
||||
-- Table: Sales_Invoice_Items
|
||||
-- ------------------------------------------
|
||||
DROP TRIGGER IF EXISTS `trg_sales_invoice_items_after_insert`;
|
||||
|
||||
CREATE TRIGGER `trg_sales_invoice_items_after_insert` AFTER
|
||||
INSERT
|
||||
ON `Sales_Invoice_Items` FOR EACH ROW begin DECLARE current_stock DECIMAL(10, 2);
|
||||
|
||||
DECLARE inventory_id INT;
|
||||
|
||||
|
||||
SELECT inventoryId INTO inventory_id
|
||||
FROM Sales_Invoices si
|
||||
WHERE si.id = NEW.invoiceId
|
||||
LIMIT 1;
|
||||
|
||||
SELECT COALESCE(quantity, 0) INTO current_stock
|
||||
FROM Stock_Balance sb
|
||||
WHERE productId = NEW.productId AND sb.inventoryId = inventory_id
|
||||
LIMIT 1;
|
||||
|
||||
|
||||
|
||||
INSERT INTO Stock_Movements (
|
||||
type,
|
||||
quantity,
|
||||
fee,
|
||||
totalCost,
|
||||
referenceType,
|
||||
referenceId,
|
||||
productId,
|
||||
inventoryId,
|
||||
avgCost,
|
||||
remainedInStock,
|
||||
createdAt
|
||||
)
|
||||
VALUES (
|
||||
'OUT',
|
||||
NEW.count,
|
||||
NEW.fee,
|
||||
NEW.total,
|
||||
'SALES',
|
||||
NEW.invoiceId,
|
||||
NEW.productId,
|
||||
inventory_id,
|
||||
CASE
|
||||
WHEN NEW.count = 0 THEN 0
|
||||
ELSE NEW.total / NEW.count
|
||||
END
|
||||
|
||||
, current_stock + NEW.count, NOW() );
|
||||
|
||||
END;
|
||||
|
||||
-- ------------------------------------------
|
||||
@@ -121,81 +211,82 @@ END;
|
||||
-- ------------------------------------------
|
||||
DROP TRIGGER IF EXISTS `trg_stock_transfer`;
|
||||
|
||||
CREATE DEFINER=`pos`@`%` TRIGGER `trg_stock_transfer` AFTER INSERT ON `Stock_Movements` FOR EACH ROW BEGIN
|
||||
|
||||
IF NEW.referenceType = 'INVENTORY_TRANSFER' THEN
|
||||
|
||||
IF NEW.type = 'IN' THEN
|
||||
INSERT INTO Stock_Balance (
|
||||
CREATE TRIGGER `trg_stock_transfer` AFTER
|
||||
INSERT
|
||||
ON `Stock_Movements` FOR EACH ROW BEGIN IF NEW.referenceType = 'INVENTORY_TRANSFER' THEN IF NEW.type = 'IN' THEN
|
||||
INSERT INTO
|
||||
Stock_Balance (
|
||||
productId,
|
||||
inventoryId,
|
||||
quantity,
|
||||
totalCost,
|
||||
avgCost,
|
||||
updatedAt
|
||||
)
|
||||
VALUES (
|
||||
)
|
||||
VALUES (
|
||||
NEW.productId,
|
||||
NEW.inventoryId,
|
||||
NEW.quantity,
|
||||
NEW.totalCost,
|
||||
CASE
|
||||
WHEN NEW.quantity = 0 THEN 0
|
||||
ELSE NEW.totalCost / NEW.quantity
|
||||
WHEN NEW.quantity = 0 THEN 0
|
||||
ELSE NEW.totalCost / NEW.quantity
|
||||
END,
|
||||
NOW()
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
quantity = quantity + NEW.quantity,
|
||||
totalCost = totalCost + NEW.totalCost,
|
||||
avgCost = CASE
|
||||
WHEN (quantity + NEW.quantity) = 0 THEN 0
|
||||
ELSE (totalCost + NEW.totalCost) / (quantity + NEW.quantity)
|
||||
END,
|
||||
updatedAt = NOW();
|
||||
END IF;
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
quantity = quantity + NEW.quantity,
|
||||
totalCost = totalCost + NEW.totalCost,
|
||||
avgCost = CASE
|
||||
WHEN (quantity + NEW.quantity) = 0 THEN 0
|
||||
ELSE (totalCost + NEW.totalCost) / (quantity + NEW.quantity)
|
||||
END,
|
||||
updatedAt = NOW();
|
||||
|
||||
END IF;
|
||||
|
||||
IF NEW.type = 'OUT' THEN
|
||||
IF NEW.type = 'OUT' THEN IF EXISTS (
|
||||
SELECT 1
|
||||
FROM Stock_Balance sb
|
||||
WHERE
|
||||
sb.productId = NEW.productId
|
||||
AND sb.inventoryId = NEW.inventoryId
|
||||
) THEN
|
||||
|
||||
UPDATE Stock_Balance sb
|
||||
SET
|
||||
sb.quantity = sb.quantity - NEW.quantity,
|
||||
sb.totalCost = sb.totalCost - (sb.avgCost * NEW.quantity),
|
||||
sb.updatedAt = NOW()
|
||||
WHERE
|
||||
sb.productId = NEW.productId
|
||||
AND sb.inventoryId = NEW.inventoryId;
|
||||
|
||||
IF EXISTS(
|
||||
SELECT 1 FROM Stock_Balance sb
|
||||
WHERE sb.productId = NEW.productId
|
||||
AND sb.inventoryId = NEW.inventoryId
|
||||
) THEN
|
||||
ELSE
|
||||
INSERT INTO
|
||||
Stock_Balance (
|
||||
productId,
|
||||
inventoryId,
|
||||
quantity,
|
||||
totalCost,
|
||||
avgCost,
|
||||
updatedAt
|
||||
)
|
||||
VALUES (
|
||||
NEW.productId,
|
||||
NEW.inventoryId,
|
||||
- NEW.quantity,
|
||||
- COALESCE(NEW.fee, 0) * NEW.quantity,
|
||||
COALESCE(NEW.fee, 0),
|
||||
NOW()
|
||||
);
|
||||
|
||||
END IF;
|
||||
|
||||
UPDATE Stock_Balance sb
|
||||
SET
|
||||
sb.quantity = sb.quantity - NEW.quantity,
|
||||
sb.totalCost = sb.totalCost - (sb.avgCost * NEW.quantity),
|
||||
sb.updatedAt = NOW()
|
||||
WHERE sb.productId = NEW.productId
|
||||
AND sb.inventoryId = NEW.inventoryId;
|
||||
END IF;
|
||||
|
||||
ELSE
|
||||
INSERT INTO Stock_Balance (
|
||||
productId,
|
||||
inventoryId,
|
||||
quantity,
|
||||
totalCost,
|
||||
avgCost,
|
||||
updatedAt
|
||||
)
|
||||
VALUES (
|
||||
NEW.productId,
|
||||
NEW.inventoryId,
|
||||
-NEW.quantity,
|
||||
-COALESCE(NEW.fee, 0) * NEW.quantity,
|
||||
COALESCE(NEW.fee, 0),
|
||||
NOW()
|
||||
);
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
END IF;
|
||||
|
||||
END IF;
|
||||
END;
|
||||
|
||||
-- ------------------------------------------
|
||||
@@ -205,22 +296,69 @@ END;
|
||||
-- ------------------------------------------
|
||||
DROP TRIGGER IF EXISTS `trg_stock_purchase_insert`;
|
||||
|
||||
CREATE DEFINER=`pos`@`%` TRIGGER `trg_stock_purchase_insert` AFTER INSERT ON `Stock_Movements` FOR EACH ROW BEGIN
|
||||
IF NEW.referenceType = 'PURCHASE' THEN
|
||||
CREATE TRIGGER `trg_stock_purchase_insert` AFTER
|
||||
INSERT
|
||||
ON `Stock_Movements` FOR EACH ROW BEGIN IF NEW.referenceType = 'PURCHASE' THEN
|
||||
|
||||
INSERT INTO Stock_Balance (productId, quantity, avgCost, totalCost, inventoryId, updatedAt)
|
||||
VALUES (
|
||||
NEW.productId,
|
||||
NEW.quantity,
|
||||
NEW.fee,
|
||||
NEW.totalCost,
|
||||
NEW.inventoryId,
|
||||
NOW()
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
quantity = quantity + NEW.quantity,
|
||||
totalCost = totalCost + NEW.totalCost,
|
||||
avgCost = totalCost / quantity;
|
||||
INSERT INTO
|
||||
Stock_Balance (
|
||||
productId,
|
||||
quantity,
|
||||
avgCost,
|
||||
totalCost,
|
||||
inventoryId,
|
||||
updatedAt
|
||||
)
|
||||
VALUES (
|
||||
NEW.productId,
|
||||
NEW.quantity,
|
||||
NEW.fee,
|
||||
NEW.totalCost,
|
||||
NEW.inventoryId,
|
||||
NOW()
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
quantity = quantity + NEW.quantity,
|
||||
totalCost = totalCost + NEW.totalCost,
|
||||
avgCost = totalCost / quantity;
|
||||
|
||||
END IF;
|
||||
|
||||
END;
|
||||
|
||||
-- ------------------------------------------
|
||||
-- Trigger: trg_stock_sale_insert
|
||||
-- Event: INSERT
|
||||
-- Table: Stock_Movements
|
||||
-- ------------------------------------------
|
||||
DROP TRIGGER IF EXISTS `trg_stock_sale_insert`;
|
||||
|
||||
CREATE TRIGGER `trg_stock_sale_insert` AFTER
|
||||
INSERT
|
||||
ON `Stock_Movements` FOR EACH ROW BEGIN IF NEW.referenceType = 'SALES' THEN
|
||||
|
||||
INSERT INTO
|
||||
Stock_Balance (
|
||||
productId,
|
||||
quantity,
|
||||
avgCost,
|
||||
totalCost,
|
||||
inventoryId,
|
||||
updatedAt
|
||||
)
|
||||
VALUES (
|
||||
NEW.productId,
|
||||
NEW.quantity,
|
||||
NEW.fee,
|
||||
NEW.totalCost,
|
||||
NEW.inventoryId,
|
||||
NOW()
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
quantity = quantity - NEW.quantity,
|
||||
totalCost = totalCost - NEW.totalCost,
|
||||
avgCost = totalCost / quantity;
|
||||
|
||||
END IF;
|
||||
|
||||
END IF;
|
||||
END;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[name]` on the table `Roles` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- CreateTable
|
||||
CREATE TABLE `Otp_Codes` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`mobileNumber` VARCHAR(20) NOT NULL,
|
||||
`code` VARCHAR(10) NOT NULL,
|
||||
`used` BOOLEAN NOT NULL DEFAULT false,
|
||||
`expiresAt` TIMESTAMP(0) NOT NULL,
|
||||
`createdAt` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
|
||||
|
||||
INDEX `Otp_Codes_mobileNumber_idx`(`mobileNumber`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Refresh_Tokens` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`tokenHash` VARCHAR(255) NOT NULL,
|
||||
`userId` INTEGER NOT NULL,
|
||||
`revoked` BOOLEAN NOT NULL DEFAULT false,
|
||||
`expiresAt` TIMESTAMP(0) NOT NULL,
|
||||
`createdAt` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
|
||||
|
||||
INDEX `Refresh_Tokens_userId_idx`(`userId`),
|
||||
PRIMARY KEY (`id`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX `Roles_name_key` ON `Roles`(`name`);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE `Refresh_Tokens` ADD CONSTRAINT `Refresh_Tokens_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `Users`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
+38
-11
@@ -10,16 +10,17 @@ datasource db {
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
mobileNumber String @unique @db.Char(11)
|
||||
password String
|
||||
firstName String
|
||||
lastName String
|
||||
roleId Int
|
||||
createdAt DateTime @default(now()) @db.Timestamp(0)
|
||||
deletedAt DateTime? @db.Timestamp(0)
|
||||
updatedAt DateTime @updatedAt @db.Timestamp(0)
|
||||
role Role @relation("User_Role", fields: [roleId], references: [id], onUpdate: NoAction)
|
||||
id Int @id @default(autoincrement())
|
||||
mobileNumber String @unique @db.Char(11)
|
||||
password String
|
||||
firstName String
|
||||
lastName String
|
||||
roleId Int
|
||||
createdAt DateTime @default(now()) @db.Timestamp(0)
|
||||
deletedAt DateTime? @db.Timestamp(0)
|
||||
updatedAt DateTime @updatedAt @db.Timestamp(0)
|
||||
role Role @relation("User_Role", fields: [roleId], references: [id], onUpdate: NoAction)
|
||||
refreshTokens RefreshToken[]
|
||||
|
||||
@@index([roleId], map: "Users_roleId_fkey")
|
||||
@@map("Users")
|
||||
@@ -27,7 +28,7 @@ model User {
|
||||
|
||||
model Role {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @db.VarChar(100)
|
||||
name String @unique() @db.VarChar(100)
|
||||
description String? @db.Text
|
||||
permissions Json?
|
||||
createdAt DateTime @default(now()) @db.Timestamp(0)
|
||||
@@ -345,6 +346,32 @@ model StockBalance {
|
||||
@@map("Stock_Balance")
|
||||
}
|
||||
|
||||
model OtpCode {
|
||||
id Int @id @default(autoincrement())
|
||||
mobileNumber String @db.VarChar(20)
|
||||
code String @db.VarChar(10)
|
||||
used Boolean @default(false)
|
||||
expiresAt DateTime @db.Timestamp(0)
|
||||
createdAt DateTime @default(now()) @db.Timestamp(0)
|
||||
|
||||
@@index([mobileNumber])
|
||||
@@map("Otp_Codes")
|
||||
}
|
||||
|
||||
model RefreshToken {
|
||||
id Int @id @default(autoincrement())
|
||||
tokenHash String @db.VarChar(255)
|
||||
userId Int
|
||||
revoked Boolean @default(false)
|
||||
expiresAt DateTime @db.Timestamp(0)
|
||||
createdAt DateTime @default(now()) @db.Timestamp(0)
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
@@index([userId])
|
||||
@@map("Refresh_Tokens")
|
||||
}
|
||||
|
||||
model InventoryTransfer {
|
||||
id Int @id @default(autoincrement())
|
||||
code String @unique @db.VarChar(100)
|
||||
|
||||
@@ -11,6 +11,24 @@ async function main() {
|
||||
})
|
||||
}
|
||||
|
||||
if ((await prisma.user.count()) === 0) {
|
||||
const adminRole = await prisma.role.findUnique({ where: { name: 'Admin' } })
|
||||
if (!adminRole) {
|
||||
return
|
||||
}
|
||||
await prisma.user.upsert({
|
||||
where: { id: 1, firstName: 'عباس', lastName: 'حسنی' },
|
||||
update: {},
|
||||
create: {
|
||||
firstName: 'عباس',
|
||||
lastName: 'حسنی',
|
||||
roleId: adminRole.id,
|
||||
mobileNumber: '09120258156',
|
||||
password: '12345678',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if ((await prisma.inventory.count()) === 0) {
|
||||
await prisma.inventory.createMany({
|
||||
data: [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
-- AUTO-GENERATED MYSQL TRIGGER DUMP
|
||||
-- Generated at: 2025-12-15T15:21:49.148Z
|
||||
-- Generated at: 2025-12-21T08:07:11.406Z
|
||||
|
||||
-- ------------------------------------------
|
||||
-- Trigger: trg_transfer_item_after_insert
|
||||
|
||||
+5
-1
@@ -1,11 +1,13 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { AppController } from './app.controller'
|
||||
import { AppService } from './app.service'
|
||||
import { AuthModule } from './auth/auth.module'
|
||||
import { CustomersModule } from './customers/customers.module'
|
||||
import { InventoriesModule } from './inventories/inventories.module'
|
||||
import { InventoryTransferItemsModule } from './inventory-transfer-items/inventory-transfer-items.module'
|
||||
import { InventoryTransfersModule } from './inventory-transfers/inventory-transfers.module'
|
||||
import { PosModule } from './pos/pos.module'
|
||||
import { CardexModule } from './modules/cardex/cardex.module'
|
||||
import { PosModule } from './modules/pos/pos.module'
|
||||
import { PrismaModule } from './prisma/prisma.module'
|
||||
import { ProductBrandsModule } from './product-brands/product-brands.module'
|
||||
import { ProductCategoriesModule } from './product-categories/product-categories.module'
|
||||
@@ -28,6 +30,7 @@ import { UsersModule } from './users/users.module'
|
||||
imports: [
|
||||
PrismaModule,
|
||||
UsersModule,
|
||||
AuthModule,
|
||||
RolesModule,
|
||||
ProductsModule,
|
||||
ProductVariantsModule,
|
||||
@@ -48,6 +51,7 @@ import { UsersModule } from './users/users.module'
|
||||
StockAdjustmentsModule,
|
||||
StockBalanceModule,
|
||||
PosModule,
|
||||
CardexModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common'
|
||||
import { ApiTags } from '@nestjs/swagger'
|
||||
import { ResponseMapper } from '../common/response/response-mapper'
|
||||
import { AuthService } from './auth.service'
|
||||
import { CurrentUser } from './current-user.decorator'
|
||||
import { RefreshTokenDto } from './dto/refresh-token.dto'
|
||||
import { SendCodeDto } from './dto/send-code.dto'
|
||||
import { VerifyCodeDto } from './dto/verify-code.dto'
|
||||
import { JwtAuthGuard } from './jwt-auth.guard'
|
||||
|
||||
@ApiTags('auth')
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private readonly auth: AuthService) {}
|
||||
|
||||
@Post('send-code')
|
||||
async sendCode(@Body() dto: SendCodeDto) {
|
||||
return this.auth.sendCode(dto)
|
||||
}
|
||||
|
||||
@Post('login')
|
||||
async login(@Body() dto: VerifyCodeDto) {
|
||||
return this.auth.loginWithCode(dto)
|
||||
}
|
||||
|
||||
@Post('refresh')
|
||||
async refresh(@Body() dto: RefreshTokenDto) {
|
||||
return this.auth.refreshToken(dto)
|
||||
}
|
||||
|
||||
@Post('logout')
|
||||
async logout(@Body() dto: RefreshTokenDto) {
|
||||
return this.auth.logout(dto.refreshToken)
|
||||
}
|
||||
|
||||
@Get('me')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async me(@CurrentUser() user: any) {
|
||||
return ResponseMapper.single(user)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { JwtModule } from '@nestjs/jwt'
|
||||
import 'dotenv/config'
|
||||
import { env } from 'prisma/config'
|
||||
import { PrismaModule } from '../prisma/prisma.module'
|
||||
import { AuthController } from './auth.controller'
|
||||
import { AuthService } from './auth.service'
|
||||
import { JwtAuthGuard } from './jwt-auth.guard'
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
PrismaModule,
|
||||
JwtModule.register({
|
||||
secret: env('JWT_SECRET') || 'secret',
|
||||
signOptions: { expiresIn: Number(env('JWT_EXPIRES_IN')) || '15m' },
|
||||
}),
|
||||
],
|
||||
providers: [AuthService, JwtAuthGuard],
|
||||
controllers: [AuthController],
|
||||
exports: [AuthService, JwtAuthGuard],
|
||||
})
|
||||
export class AuthModule {}
|
||||
@@ -0,0 +1,126 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common'
|
||||
import { JwtService } from '@nestjs/jwt'
|
||||
import { createHash, randomBytes } from 'crypto'
|
||||
import 'dotenv/config'
|
||||
import { env } from 'prisma/config'
|
||||
import { ResponseMapper } from '../common/response/response-mapper'
|
||||
import { PrismaService } from '../prisma/prisma.service'
|
||||
import { RefreshTokenDto } from './dto/refresh-token.dto'
|
||||
import { SendCodeDto } from './dto/send-code.dto'
|
||||
import { VerifyCodeDto } from './dto/verify-code.dto'
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
private readonly OTP_TTL_MINUTES = 2
|
||||
private readonly ACCESS_TOKEN_EXP = Number(env('JWT_EXPIRES_IN')) || '15m'
|
||||
private readonly REFRESH_TOKEN_EXP_DAYS = 30
|
||||
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private jwt: JwtService,
|
||||
) {}
|
||||
|
||||
private generateCode() {
|
||||
// For development you may want fixed code via env, otherwise random 5-digit
|
||||
const staticCode = process.env.OTP_STATIC_CODE
|
||||
if (staticCode) return staticCode
|
||||
return Math.floor(10000 + Math.random() * 90000).toString()
|
||||
}
|
||||
|
||||
async sendCode(dto: SendCodeDto) {
|
||||
const { mobileNumber } = dto
|
||||
const user = await this.prisma.user.findFirst({ where: { mobileNumber } })
|
||||
if (!user) {
|
||||
throw new NotFoundException('کاربری با این شماره تماس یافت نشد')
|
||||
}
|
||||
const code = this.generateCode()
|
||||
const expiresAt = new Date(Date.now() + this.OTP_TTL_MINUTES * 60 * 1000)
|
||||
|
||||
await this.prisma.otpCode.create({
|
||||
data: {
|
||||
mobileNumber,
|
||||
code,
|
||||
expiresAt,
|
||||
},
|
||||
})
|
||||
|
||||
// TODO: integrate SMS provider. For now return the code for dev.
|
||||
return {
|
||||
ok: true,
|
||||
}
|
||||
}
|
||||
|
||||
async loginWithCode(dto: VerifyCodeDto) {
|
||||
const { mobileNumber, code } = dto
|
||||
const user = await this.prisma.user.findFirst({
|
||||
where: { mobileNumber },
|
||||
include: {
|
||||
role: true,
|
||||
},
|
||||
})
|
||||
if (!user) {
|
||||
throw new NotFoundException('کاربری با این شماره تماس یافت نشد')
|
||||
}
|
||||
|
||||
const otp = await this.prisma.otpCode.findFirst({
|
||||
where: { mobileNumber, used: false },
|
||||
})
|
||||
|
||||
if (!otp) throw new BadRequestException('اطلاعات ورودی درست نیست')
|
||||
if (otp.used) throw new BadRequestException('کد قبلن استفاده شده است')
|
||||
if (otp.expiresAt.getTime() < Date.now())
|
||||
throw new BadRequestException('کد وارد شده منقضی شده است')
|
||||
if (otp.code !== code) throw new BadRequestException('کد وارد شده اشتباه است')
|
||||
|
||||
// mark used
|
||||
await this.prisma.otpCode.update({ where: { id: otp.id }, data: { used: true } })
|
||||
|
||||
const accessToken = this.jwt.sign(
|
||||
{ sub: user.id, mobileNumber },
|
||||
{ expiresIn: this.ACCESS_TOKEN_EXP },
|
||||
)
|
||||
const refreshTokenPlain = randomBytes(48).toString('hex')
|
||||
const tokenHash = createHash('sha256').update(refreshTokenPlain).digest('hex')
|
||||
const expiresAt = new Date(
|
||||
Date.now() + this.REFRESH_TOKEN_EXP_DAYS * 24 * 60 * 60 * 1000,
|
||||
)
|
||||
|
||||
await this.prisma.refreshToken.create({
|
||||
data: { tokenHash, userId: user.id, expiresAt },
|
||||
})
|
||||
|
||||
return ResponseMapper.single({ accessToken, refreshToken: refreshTokenPlain, user })
|
||||
}
|
||||
|
||||
async refreshToken(dto: RefreshTokenDto) {
|
||||
const { refreshToken } = dto
|
||||
const tokenHash = createHash('sha256').update(refreshToken).digest('hex')
|
||||
const record = await this.prisma.refreshToken.findFirst({ where: { tokenHash } })
|
||||
if (!record || record.revoked || record.expiresAt.getTime() < Date.now()) {
|
||||
throw new UnauthorizedException('Invalid refresh token')
|
||||
}
|
||||
|
||||
const user = await this.prisma.user.findUnique({ where: { id: record.userId } })
|
||||
if (!user) throw new UnauthorizedException('User not found')
|
||||
|
||||
const accessToken = this.jwt.sign(
|
||||
{ sub: user.id, mobileNumber: user.mobileNumber },
|
||||
{ expiresIn: this.ACCESS_TOKEN_EXP },
|
||||
)
|
||||
return { accessToken }
|
||||
}
|
||||
|
||||
async logout(refreshToken: string) {
|
||||
const tokenHash = createHash('sha256').update(refreshToken).digest('hex')
|
||||
await this.prisma.refreshToken.updateMany({
|
||||
where: { tokenHash },
|
||||
data: { revoked: true },
|
||||
})
|
||||
return { ok: true }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common'
|
||||
|
||||
export const CurrentUser = createParamDecorator(
|
||||
(data: unknown, ctx: ExecutionContext) => {
|
||||
const req = ctx.switchToHttp().getRequest()
|
||||
return req.user
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from '@nestjs/swagger'
|
||||
import { IsString } from 'class-validator'
|
||||
|
||||
export class LoginDto {
|
||||
@ApiProperty({ description: 'Mobile number used as username', example: '09123456789' })
|
||||
@IsString()
|
||||
mobileNumber: string
|
||||
|
||||
@ApiProperty({ description: 'OTP password (one-time password)', example: '12345' })
|
||||
@IsString()
|
||||
password: string
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { ApiProperty } from '@nestjs/swagger'
|
||||
import { IsString } from 'class-validator'
|
||||
|
||||
export class RefreshTokenDto {
|
||||
@ApiProperty()
|
||||
@IsString()
|
||||
refreshToken: string
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { ApiProperty } from '@nestjs/swagger'
|
||||
import { IsString } from 'class-validator'
|
||||
|
||||
export class SendCodeDto {
|
||||
@ApiProperty({ example: '09123456789' })
|
||||
@IsString()
|
||||
mobileNumber: string
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from '@nestjs/swagger'
|
||||
import { IsString } from 'class-validator'
|
||||
|
||||
export class VerifyCodeDto {
|
||||
@ApiProperty({ example: '09123456789' })
|
||||
@IsString()
|
||||
mobileNumber: string
|
||||
|
||||
@ApiProperty({ example: '12345' })
|
||||
@IsString()
|
||||
code: string
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common'
|
||||
import { JwtService } from '@nestjs/jwt'
|
||||
import { Request } from 'express'
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard implements CanActivate {
|
||||
constructor(private jwt: JwtService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const req = context.switchToHttp().getRequest<Request>()
|
||||
const auth = req.headers.authorization
|
||||
if (!auth) throw new UnauthorizedException('Missing Authorization header')
|
||||
|
||||
const parts = auth.split(' ')
|
||||
if (parts.length !== 2 || parts[0] !== 'Bearer') {
|
||||
throw new UnauthorizedException('Invalid Authorization header format')
|
||||
}
|
||||
|
||||
const token = parts[1]
|
||||
try {
|
||||
const payload = this.jwt.verify(token, {
|
||||
secret: process.env.JWT_SECRET || 'secret',
|
||||
})
|
||||
;(req as any).user = payload
|
||||
return true
|
||||
} catch (err) {
|
||||
throw new UnauthorizedException('Invalid or expired token')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,16 @@ export type StockMovement = Prisma.StockMovementModel
|
||||
*
|
||||
*/
|
||||
export type StockBalance = Prisma.StockBalanceModel
|
||||
/**
|
||||
* Model OtpCode
|
||||
*
|
||||
*/
|
||||
export type OtpCode = Prisma.OtpCodeModel
|
||||
/**
|
||||
* Model RefreshToken
|
||||
*
|
||||
*/
|
||||
export type RefreshToken = Prisma.RefreshTokenModel
|
||||
/**
|
||||
* Model InventoryTransfer
|
||||
*
|
||||
|
||||
@@ -127,6 +127,16 @@ export type StockMovement = Prisma.StockMovementModel
|
||||
*
|
||||
*/
|
||||
export type StockBalance = Prisma.StockBalanceModel
|
||||
/**
|
||||
* Model OtpCode
|
||||
*
|
||||
*/
|
||||
export type OtpCode = Prisma.OtpCodeModel
|
||||
/**
|
||||
* Model RefreshToken
|
||||
*
|
||||
*/
|
||||
export type RefreshToken = Prisma.RefreshTokenModel
|
||||
/**
|
||||
* Model InventoryTransfer
|
||||
*
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -402,6 +402,8 @@ export const ModelName = {
|
||||
SalesInvoiceItem: 'SalesInvoiceItem',
|
||||
StockMovement: 'StockMovement',
|
||||
StockBalance: 'StockBalance',
|
||||
OtpCode: 'OtpCode',
|
||||
RefreshToken: 'RefreshToken',
|
||||
InventoryTransfer: 'InventoryTransfer',
|
||||
InventoryTransferItem: 'InventoryTransferItem',
|
||||
StockAdjustment: 'StockAdjustment',
|
||||
@@ -424,7 +426,7 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
||||
omit: GlobalOmitOptions
|
||||
}
|
||||
meta: {
|
||||
modelProps: "user" | "role" | "productVariant" | "product" | "productBrand" | "productCategory" | "supplier" | "customer" | "inventory" | "store" | "productCharge" | "order" | "purchaseReceipt" | "purchaseReceiptItem" | "salesInvoice" | "salesInvoiceItem" | "stockMovement" | "stockBalance" | "inventoryTransfer" | "inventoryTransferItem" | "stockAdjustment" | "triggerLog" | "inventory_overview" | "stock_cardex" | "stock_view"
|
||||
modelProps: "user" | "role" | "productVariant" | "product" | "productBrand" | "productCategory" | "supplier" | "customer" | "inventory" | "store" | "productCharge" | "order" | "purchaseReceipt" | "purchaseReceiptItem" | "salesInvoice" | "salesInvoiceItem" | "stockMovement" | "stockBalance" | "otpCode" | "refreshToken" | "inventoryTransfer" | "inventoryTransferItem" | "stockAdjustment" | "triggerLog" | "inventory_overview" | "stock_cardex" | "stock_view"
|
||||
txIsolationLevel: TransactionIsolationLevel
|
||||
}
|
||||
model: {
|
||||
@@ -1616,6 +1618,138 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
||||
}
|
||||
}
|
||||
}
|
||||
OtpCode: {
|
||||
payload: Prisma.$OtpCodePayload<ExtArgs>
|
||||
fields: Prisma.OtpCodeFieldRefs
|
||||
operations: {
|
||||
findUnique: {
|
||||
args: Prisma.OtpCodeFindUniqueArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OtpCodePayload> | null
|
||||
}
|
||||
findUniqueOrThrow: {
|
||||
args: Prisma.OtpCodeFindUniqueOrThrowArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OtpCodePayload>
|
||||
}
|
||||
findFirst: {
|
||||
args: Prisma.OtpCodeFindFirstArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OtpCodePayload> | null
|
||||
}
|
||||
findFirstOrThrow: {
|
||||
args: Prisma.OtpCodeFindFirstOrThrowArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OtpCodePayload>
|
||||
}
|
||||
findMany: {
|
||||
args: Prisma.OtpCodeFindManyArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OtpCodePayload>[]
|
||||
}
|
||||
create: {
|
||||
args: Prisma.OtpCodeCreateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OtpCodePayload>
|
||||
}
|
||||
createMany: {
|
||||
args: Prisma.OtpCodeCreateManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
delete: {
|
||||
args: Prisma.OtpCodeDeleteArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OtpCodePayload>
|
||||
}
|
||||
update: {
|
||||
args: Prisma.OtpCodeUpdateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OtpCodePayload>
|
||||
}
|
||||
deleteMany: {
|
||||
args: Prisma.OtpCodeDeleteManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
updateMany: {
|
||||
args: Prisma.OtpCodeUpdateManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
upsert: {
|
||||
args: Prisma.OtpCodeUpsertArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$OtpCodePayload>
|
||||
}
|
||||
aggregate: {
|
||||
args: Prisma.OtpCodeAggregateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.AggregateOtpCode>
|
||||
}
|
||||
groupBy: {
|
||||
args: Prisma.OtpCodeGroupByArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.OtpCodeGroupByOutputType>[]
|
||||
}
|
||||
count: {
|
||||
args: Prisma.OtpCodeCountArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.OtpCodeCountAggregateOutputType> | number
|
||||
}
|
||||
}
|
||||
}
|
||||
RefreshToken: {
|
||||
payload: Prisma.$RefreshTokenPayload<ExtArgs>
|
||||
fields: Prisma.RefreshTokenFieldRefs
|
||||
operations: {
|
||||
findUnique: {
|
||||
args: Prisma.RefreshTokenFindUniqueArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RefreshTokenPayload> | null
|
||||
}
|
||||
findUniqueOrThrow: {
|
||||
args: Prisma.RefreshTokenFindUniqueOrThrowArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RefreshTokenPayload>
|
||||
}
|
||||
findFirst: {
|
||||
args: Prisma.RefreshTokenFindFirstArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RefreshTokenPayload> | null
|
||||
}
|
||||
findFirstOrThrow: {
|
||||
args: Prisma.RefreshTokenFindFirstOrThrowArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RefreshTokenPayload>
|
||||
}
|
||||
findMany: {
|
||||
args: Prisma.RefreshTokenFindManyArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RefreshTokenPayload>[]
|
||||
}
|
||||
create: {
|
||||
args: Prisma.RefreshTokenCreateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RefreshTokenPayload>
|
||||
}
|
||||
createMany: {
|
||||
args: Prisma.RefreshTokenCreateManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
delete: {
|
||||
args: Prisma.RefreshTokenDeleteArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RefreshTokenPayload>
|
||||
}
|
||||
update: {
|
||||
args: Prisma.RefreshTokenUpdateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RefreshTokenPayload>
|
||||
}
|
||||
deleteMany: {
|
||||
args: Prisma.RefreshTokenDeleteManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
updateMany: {
|
||||
args: Prisma.RefreshTokenUpdateManyArgs<ExtArgs>
|
||||
result: BatchPayload
|
||||
}
|
||||
upsert: {
|
||||
args: Prisma.RefreshTokenUpsertArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.PayloadToResult<Prisma.$RefreshTokenPayload>
|
||||
}
|
||||
aggregate: {
|
||||
args: Prisma.RefreshTokenAggregateArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.AggregateRefreshToken>
|
||||
}
|
||||
groupBy: {
|
||||
args: Prisma.RefreshTokenGroupByArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.RefreshTokenGroupByOutputType>[]
|
||||
}
|
||||
count: {
|
||||
args: Prisma.RefreshTokenCountArgs<ExtArgs>
|
||||
result: runtime.Types.Utils.Optional<Prisma.RefreshTokenCountAggregateOutputType> | number
|
||||
}
|
||||
}
|
||||
}
|
||||
InventoryTransfer: {
|
||||
payload: Prisma.$InventoryTransferPayload<ExtArgs>
|
||||
fields: Prisma.InventoryTransferFieldRefs
|
||||
@@ -2289,6 +2423,30 @@ export const StockBalanceScalarFieldEnum = {
|
||||
export type StockBalanceScalarFieldEnum = (typeof StockBalanceScalarFieldEnum)[keyof typeof StockBalanceScalarFieldEnum]
|
||||
|
||||
|
||||
export const OtpCodeScalarFieldEnum = {
|
||||
id: 'id',
|
||||
mobileNumber: 'mobileNumber',
|
||||
code: 'code',
|
||||
used: 'used',
|
||||
expiresAt: 'expiresAt',
|
||||
createdAt: 'createdAt'
|
||||
} as const
|
||||
|
||||
export type OtpCodeScalarFieldEnum = (typeof OtpCodeScalarFieldEnum)[keyof typeof OtpCodeScalarFieldEnum]
|
||||
|
||||
|
||||
export const RefreshTokenScalarFieldEnum = {
|
||||
id: 'id',
|
||||
tokenHash: 'tokenHash',
|
||||
userId: 'userId',
|
||||
revoked: 'revoked',
|
||||
expiresAt: 'expiresAt',
|
||||
createdAt: 'createdAt'
|
||||
} as const
|
||||
|
||||
export type RefreshTokenScalarFieldEnum = (typeof RefreshTokenScalarFieldEnum)[keyof typeof RefreshTokenScalarFieldEnum]
|
||||
|
||||
|
||||
export const InventoryTransferScalarFieldEnum = {
|
||||
id: 'id',
|
||||
code: 'code',
|
||||
@@ -2553,6 +2711,21 @@ export const StockMovementOrderByRelevanceFieldEnum = {
|
||||
export type StockMovementOrderByRelevanceFieldEnum = (typeof StockMovementOrderByRelevanceFieldEnum)[keyof typeof StockMovementOrderByRelevanceFieldEnum]
|
||||
|
||||
|
||||
export const OtpCodeOrderByRelevanceFieldEnum = {
|
||||
mobileNumber: 'mobileNumber',
|
||||
code: 'code'
|
||||
} as const
|
||||
|
||||
export type OtpCodeOrderByRelevanceFieldEnum = (typeof OtpCodeOrderByRelevanceFieldEnum)[keyof typeof OtpCodeOrderByRelevanceFieldEnum]
|
||||
|
||||
|
||||
export const RefreshTokenOrderByRelevanceFieldEnum = {
|
||||
tokenHash: 'tokenHash'
|
||||
} as const
|
||||
|
||||
export type RefreshTokenOrderByRelevanceFieldEnum = (typeof RefreshTokenOrderByRelevanceFieldEnum)[keyof typeof RefreshTokenOrderByRelevanceFieldEnum]
|
||||
|
||||
|
||||
export const InventoryTransferOrderByRelevanceFieldEnum = {
|
||||
code: 'code',
|
||||
description: 'description'
|
||||
@@ -2778,6 +2951,8 @@ export type GlobalOmitConfig = {
|
||||
salesInvoiceItem?: Prisma.SalesInvoiceItemOmit
|
||||
stockMovement?: Prisma.StockMovementOmit
|
||||
stockBalance?: Prisma.StockBalanceOmit
|
||||
otpCode?: Prisma.OtpCodeOmit
|
||||
refreshToken?: Prisma.RefreshTokenOmit
|
||||
inventoryTransfer?: Prisma.InventoryTransferOmit
|
||||
inventoryTransferItem?: Prisma.InventoryTransferItemOmit
|
||||
stockAdjustment?: Prisma.StockAdjustmentOmit
|
||||
|
||||
@@ -69,6 +69,8 @@ export const ModelName = {
|
||||
SalesInvoiceItem: 'SalesInvoiceItem',
|
||||
StockMovement: 'StockMovement',
|
||||
StockBalance: 'StockBalance',
|
||||
OtpCode: 'OtpCode',
|
||||
RefreshToken: 'RefreshToken',
|
||||
InventoryTransfer: 'InventoryTransfer',
|
||||
InventoryTransferItem: 'InventoryTransferItem',
|
||||
StockAdjustment: 'StockAdjustment',
|
||||
@@ -374,6 +376,30 @@ export const StockBalanceScalarFieldEnum = {
|
||||
export type StockBalanceScalarFieldEnum = (typeof StockBalanceScalarFieldEnum)[keyof typeof StockBalanceScalarFieldEnum]
|
||||
|
||||
|
||||
export const OtpCodeScalarFieldEnum = {
|
||||
id: 'id',
|
||||
mobileNumber: 'mobileNumber',
|
||||
code: 'code',
|
||||
used: 'used',
|
||||
expiresAt: 'expiresAt',
|
||||
createdAt: 'createdAt'
|
||||
} as const
|
||||
|
||||
export type OtpCodeScalarFieldEnum = (typeof OtpCodeScalarFieldEnum)[keyof typeof OtpCodeScalarFieldEnum]
|
||||
|
||||
|
||||
export const RefreshTokenScalarFieldEnum = {
|
||||
id: 'id',
|
||||
tokenHash: 'tokenHash',
|
||||
userId: 'userId',
|
||||
revoked: 'revoked',
|
||||
expiresAt: 'expiresAt',
|
||||
createdAt: 'createdAt'
|
||||
} as const
|
||||
|
||||
export type RefreshTokenScalarFieldEnum = (typeof RefreshTokenScalarFieldEnum)[keyof typeof RefreshTokenScalarFieldEnum]
|
||||
|
||||
|
||||
export const InventoryTransferScalarFieldEnum = {
|
||||
id: 'id',
|
||||
code: 'code',
|
||||
@@ -638,6 +664,21 @@ export const StockMovementOrderByRelevanceFieldEnum = {
|
||||
export type StockMovementOrderByRelevanceFieldEnum = (typeof StockMovementOrderByRelevanceFieldEnum)[keyof typeof StockMovementOrderByRelevanceFieldEnum]
|
||||
|
||||
|
||||
export const OtpCodeOrderByRelevanceFieldEnum = {
|
||||
mobileNumber: 'mobileNumber',
|
||||
code: 'code'
|
||||
} as const
|
||||
|
||||
export type OtpCodeOrderByRelevanceFieldEnum = (typeof OtpCodeOrderByRelevanceFieldEnum)[keyof typeof OtpCodeOrderByRelevanceFieldEnum]
|
||||
|
||||
|
||||
export const RefreshTokenOrderByRelevanceFieldEnum = {
|
||||
tokenHash: 'tokenHash'
|
||||
} as const
|
||||
|
||||
export type RefreshTokenOrderByRelevanceFieldEnum = (typeof RefreshTokenOrderByRelevanceFieldEnum)[keyof typeof RefreshTokenOrderByRelevanceFieldEnum]
|
||||
|
||||
|
||||
export const InventoryTransferOrderByRelevanceFieldEnum = {
|
||||
code: 'code',
|
||||
description: 'description'
|
||||
|
||||
@@ -26,6 +26,8 @@ export type * from './models/SalesInvoice.js'
|
||||
export type * from './models/SalesInvoiceItem.js'
|
||||
export type * from './models/StockMovement.js'
|
||||
export type * from './models/StockBalance.js'
|
||||
export type * from './models/OtpCode.js'
|
||||
export type * from './models/RefreshToken.js'
|
||||
export type * from './models/InventoryTransfer.js'
|
||||
export type * from './models/InventoryTransferItem.js'
|
||||
export type * from './models/StockAdjustment.js'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -245,17 +245,17 @@ export type RoleOrderByWithRelationInput = {
|
||||
|
||||
export type RoleWhereUniqueInput = Prisma.AtLeast<{
|
||||
id?: number
|
||||
name?: string
|
||||
AND?: Prisma.RoleWhereInput | Prisma.RoleWhereInput[]
|
||||
OR?: Prisma.RoleWhereInput[]
|
||||
NOT?: Prisma.RoleWhereInput | Prisma.RoleWhereInput[]
|
||||
name?: Prisma.StringFilter<"Role"> | string
|
||||
description?: Prisma.StringNullableFilter<"Role"> | string | null
|
||||
permissions?: Prisma.JsonNullableFilter<"Role">
|
||||
createdAt?: Prisma.DateTimeFilter<"Role"> | Date | string
|
||||
updatedAt?: Prisma.DateTimeFilter<"Role"> | Date | string
|
||||
deletedAt?: Prisma.DateTimeNullableFilter<"Role"> | Date | string | null
|
||||
users?: Prisma.UserListRelationFilter
|
||||
}, "id">
|
||||
}, "id" | "name">
|
||||
|
||||
export type RoleOrderByWithAggregationInput = {
|
||||
id?: Prisma.SortOrder
|
||||
|
||||
@@ -253,6 +253,7 @@ export type UserWhereInput = {
|
||||
deletedAt?: Prisma.DateTimeNullableFilter<"User"> | Date | string | null
|
||||
updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string
|
||||
role?: Prisma.XOR<Prisma.RoleScalarRelationFilter, Prisma.RoleWhereInput>
|
||||
refreshTokens?: Prisma.RefreshTokenListRelationFilter
|
||||
}
|
||||
|
||||
export type UserOrderByWithRelationInput = {
|
||||
@@ -266,6 +267,7 @@ export type UserOrderByWithRelationInput = {
|
||||
deletedAt?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
updatedAt?: Prisma.SortOrder
|
||||
role?: Prisma.RoleOrderByWithRelationInput
|
||||
refreshTokens?: Prisma.RefreshTokenOrderByRelationAggregateInput
|
||||
_relevance?: Prisma.UserOrderByRelevanceInput
|
||||
}
|
||||
|
||||
@@ -283,6 +285,7 @@ export type UserWhereUniqueInput = Prisma.AtLeast<{
|
||||
deletedAt?: Prisma.DateTimeNullableFilter<"User"> | Date | string | null
|
||||
updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string
|
||||
role?: Prisma.XOR<Prisma.RoleScalarRelationFilter, Prisma.RoleWhereInput>
|
||||
refreshTokens?: Prisma.RefreshTokenListRelationFilter
|
||||
}, "id" | "mobileNumber">
|
||||
|
||||
export type UserOrderByWithAggregationInput = {
|
||||
@@ -326,6 +329,7 @@ export type UserCreateInput = {
|
||||
deletedAt?: Date | string | null
|
||||
updatedAt?: Date | string
|
||||
role: Prisma.RoleCreateNestedOneWithoutUsersInput
|
||||
refreshTokens?: Prisma.RefreshTokenCreateNestedManyWithoutUserInput
|
||||
}
|
||||
|
||||
export type UserUncheckedCreateInput = {
|
||||
@@ -338,6 +342,7 @@ export type UserUncheckedCreateInput = {
|
||||
createdAt?: Date | string
|
||||
deletedAt?: Date | string | null
|
||||
updatedAt?: Date | string
|
||||
refreshTokens?: Prisma.RefreshTokenUncheckedCreateNestedManyWithoutUserInput
|
||||
}
|
||||
|
||||
export type UserUpdateInput = {
|
||||
@@ -349,6 +354,7 @@ export type UserUpdateInput = {
|
||||
deletedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
role?: Prisma.RoleUpdateOneRequiredWithoutUsersNestedInput
|
||||
refreshTokens?: Prisma.RefreshTokenUpdateManyWithoutUserNestedInput
|
||||
}
|
||||
|
||||
export type UserUncheckedUpdateInput = {
|
||||
@@ -361,6 +367,7 @@ export type UserUncheckedUpdateInput = {
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
deletedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
refreshTokens?: Prisma.RefreshTokenUncheckedUpdateManyWithoutUserNestedInput
|
||||
}
|
||||
|
||||
export type UserCreateManyInput = {
|
||||
@@ -459,6 +466,11 @@ export type UserOrderByRelationAggregateInput = {
|
||||
_count?: Prisma.SortOrder
|
||||
}
|
||||
|
||||
export type UserScalarRelationFilter = {
|
||||
is?: Prisma.UserWhereInput
|
||||
isNot?: Prisma.UserWhereInput
|
||||
}
|
||||
|
||||
export type StringFieldUpdateOperationsInput = {
|
||||
set?: string
|
||||
}
|
||||
@@ -521,6 +533,20 @@ export type UserUncheckedUpdateManyWithoutRoleNestedInput = {
|
||||
deleteMany?: Prisma.UserScalarWhereInput | Prisma.UserScalarWhereInput[]
|
||||
}
|
||||
|
||||
export type UserCreateNestedOneWithoutRefreshTokensInput = {
|
||||
create?: Prisma.XOR<Prisma.UserCreateWithoutRefreshTokensInput, Prisma.UserUncheckedCreateWithoutRefreshTokensInput>
|
||||
connectOrCreate?: Prisma.UserCreateOrConnectWithoutRefreshTokensInput
|
||||
connect?: Prisma.UserWhereUniqueInput
|
||||
}
|
||||
|
||||
export type UserUpdateOneRequiredWithoutRefreshTokensNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.UserCreateWithoutRefreshTokensInput, Prisma.UserUncheckedCreateWithoutRefreshTokensInput>
|
||||
connectOrCreate?: Prisma.UserCreateOrConnectWithoutRefreshTokensInput
|
||||
upsert?: Prisma.UserUpsertWithoutRefreshTokensInput
|
||||
connect?: Prisma.UserWhereUniqueInput
|
||||
update?: Prisma.XOR<Prisma.XOR<Prisma.UserUpdateToOneWithWhereWithoutRefreshTokensInput, Prisma.UserUpdateWithoutRefreshTokensInput>, Prisma.UserUncheckedUpdateWithoutRefreshTokensInput>
|
||||
}
|
||||
|
||||
export type UserCreateWithoutRoleInput = {
|
||||
mobileNumber: string
|
||||
password: string
|
||||
@@ -529,6 +555,7 @@ export type UserCreateWithoutRoleInput = {
|
||||
createdAt?: Date | string
|
||||
deletedAt?: Date | string | null
|
||||
updatedAt?: Date | string
|
||||
refreshTokens?: Prisma.RefreshTokenCreateNestedManyWithoutUserInput
|
||||
}
|
||||
|
||||
export type UserUncheckedCreateWithoutRoleInput = {
|
||||
@@ -540,6 +567,7 @@ export type UserUncheckedCreateWithoutRoleInput = {
|
||||
createdAt?: Date | string
|
||||
deletedAt?: Date | string | null
|
||||
updatedAt?: Date | string
|
||||
refreshTokens?: Prisma.RefreshTokenUncheckedCreateNestedManyWithoutUserInput
|
||||
}
|
||||
|
||||
export type UserCreateOrConnectWithoutRoleInput = {
|
||||
@@ -583,6 +611,68 @@ export type UserScalarWhereInput = {
|
||||
updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string
|
||||
}
|
||||
|
||||
export type UserCreateWithoutRefreshTokensInput = {
|
||||
mobileNumber: string
|
||||
password: string
|
||||
firstName: string
|
||||
lastName: string
|
||||
createdAt?: Date | string
|
||||
deletedAt?: Date | string | null
|
||||
updatedAt?: Date | string
|
||||
role: Prisma.RoleCreateNestedOneWithoutUsersInput
|
||||
}
|
||||
|
||||
export type UserUncheckedCreateWithoutRefreshTokensInput = {
|
||||
id?: number
|
||||
mobileNumber: string
|
||||
password: string
|
||||
firstName: string
|
||||
lastName: string
|
||||
roleId: number
|
||||
createdAt?: Date | string
|
||||
deletedAt?: Date | string | null
|
||||
updatedAt?: Date | string
|
||||
}
|
||||
|
||||
export type UserCreateOrConnectWithoutRefreshTokensInput = {
|
||||
where: Prisma.UserWhereUniqueInput
|
||||
create: Prisma.XOR<Prisma.UserCreateWithoutRefreshTokensInput, Prisma.UserUncheckedCreateWithoutRefreshTokensInput>
|
||||
}
|
||||
|
||||
export type UserUpsertWithoutRefreshTokensInput = {
|
||||
update: Prisma.XOR<Prisma.UserUpdateWithoutRefreshTokensInput, Prisma.UserUncheckedUpdateWithoutRefreshTokensInput>
|
||||
create: Prisma.XOR<Prisma.UserCreateWithoutRefreshTokensInput, Prisma.UserUncheckedCreateWithoutRefreshTokensInput>
|
||||
where?: Prisma.UserWhereInput
|
||||
}
|
||||
|
||||
export type UserUpdateToOneWithWhereWithoutRefreshTokensInput = {
|
||||
where?: Prisma.UserWhereInput
|
||||
data: Prisma.XOR<Prisma.UserUpdateWithoutRefreshTokensInput, Prisma.UserUncheckedUpdateWithoutRefreshTokensInput>
|
||||
}
|
||||
|
||||
export type UserUpdateWithoutRefreshTokensInput = {
|
||||
mobileNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
password?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
deletedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
role?: Prisma.RoleUpdateOneRequiredWithoutUsersNestedInput
|
||||
}
|
||||
|
||||
export type UserUncheckedUpdateWithoutRefreshTokensInput = {
|
||||
id?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
mobileNumber?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
password?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
firstName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
lastName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
roleId?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
deletedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
}
|
||||
|
||||
export type UserCreateManyRoleInput = {
|
||||
id?: number
|
||||
mobileNumber: string
|
||||
@@ -602,6 +692,7 @@ export type UserUpdateWithoutRoleInput = {
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
deletedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
refreshTokens?: Prisma.RefreshTokenUpdateManyWithoutUserNestedInput
|
||||
}
|
||||
|
||||
export type UserUncheckedUpdateWithoutRoleInput = {
|
||||
@@ -613,6 +704,7 @@ export type UserUncheckedUpdateWithoutRoleInput = {
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
deletedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
refreshTokens?: Prisma.RefreshTokenUncheckedUpdateManyWithoutUserNestedInput
|
||||
}
|
||||
|
||||
export type UserUncheckedUpdateManyWithoutRoleInput = {
|
||||
@@ -627,6 +719,35 @@ export type UserUncheckedUpdateManyWithoutRoleInput = {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Count Type UserCountOutputType
|
||||
*/
|
||||
|
||||
export type UserCountOutputType = {
|
||||
refreshTokens: number
|
||||
}
|
||||
|
||||
export type UserCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
refreshTokens?: boolean | UserCountOutputTypeCountRefreshTokensArgs
|
||||
}
|
||||
|
||||
/**
|
||||
* UserCountOutputType without action
|
||||
*/
|
||||
export type UserCountOutputTypeDefaultArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
/**
|
||||
* Select specific fields to fetch from the UserCountOutputType
|
||||
*/
|
||||
select?: Prisma.UserCountOutputTypeSelect<ExtArgs> | null
|
||||
}
|
||||
|
||||
/**
|
||||
* UserCountOutputType without action
|
||||
*/
|
||||
export type UserCountOutputTypeCountRefreshTokensArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
where?: Prisma.RefreshTokenWhereInput
|
||||
}
|
||||
|
||||
|
||||
export type UserSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||
id?: boolean
|
||||
@@ -639,6 +760,8 @@ export type UserSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = r
|
||||
deletedAt?: boolean
|
||||
updatedAt?: boolean
|
||||
role?: boolean | Prisma.RoleDefaultArgs<ExtArgs>
|
||||
refreshTokens?: boolean | Prisma.User$refreshTokensArgs<ExtArgs>
|
||||
_count?: boolean | Prisma.UserCountOutputTypeDefaultArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["user"]>
|
||||
|
||||
|
||||
@@ -658,12 +781,15 @@ export type UserSelectScalar = {
|
||||
export type UserOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "mobileNumber" | "password" | "firstName" | "lastName" | "roleId" | "createdAt" | "deletedAt" | "updatedAt", ExtArgs["result"]["user"]>
|
||||
export type UserInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
role?: boolean | Prisma.RoleDefaultArgs<ExtArgs>
|
||||
refreshTokens?: boolean | Prisma.User$refreshTokensArgs<ExtArgs>
|
||||
_count?: boolean | Prisma.UserCountOutputTypeDefaultArgs<ExtArgs>
|
||||
}
|
||||
|
||||
export type $UserPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
name: "User"
|
||||
objects: {
|
||||
role: Prisma.$RolePayload<ExtArgs>
|
||||
refreshTokens: Prisma.$RefreshTokenPayload<ExtArgs>[]
|
||||
}
|
||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||
id: number
|
||||
@@ -1016,6 +1142,7 @@ readonly fields: UserFieldRefs;
|
||||
export interface Prisma__UserClient<T, Null = never, ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs, GlobalOmitOptions = {}> extends Prisma.PrismaPromise<T> {
|
||||
readonly [Symbol.toStringTag]: "PrismaPromise"
|
||||
role<T extends Prisma.RoleDefaultArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.RoleDefaultArgs<ExtArgs>>): Prisma.Prisma__RoleClient<runtime.Types.Result.GetResult<Prisma.$RolePayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions>
|
||||
refreshTokens<T extends Prisma.User$refreshTokensArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$refreshTokensArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$RefreshTokenPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
/**
|
||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||
@@ -1396,6 +1523,30 @@ export type UserDeleteManyArgs<ExtArgs extends runtime.Types.Extensions.Internal
|
||||
limit?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* User.refreshTokens
|
||||
*/
|
||||
export type User$refreshTokensArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
/**
|
||||
* Select specific fields to fetch from the RefreshToken
|
||||
*/
|
||||
select?: Prisma.RefreshTokenSelect<ExtArgs> | null
|
||||
/**
|
||||
* Omit specific fields from the RefreshToken
|
||||
*/
|
||||
omit?: Prisma.RefreshTokenOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.RefreshTokenInclude<ExtArgs> | null
|
||||
where?: Prisma.RefreshTokenWhereInput
|
||||
orderBy?: Prisma.RefreshTokenOrderByWithRelationInput | Prisma.RefreshTokenOrderByWithRelationInput[]
|
||||
cursor?: Prisma.RefreshTokenWhereUniqueInput
|
||||
take?: number
|
||||
skip?: number
|
||||
distinct?: Prisma.RefreshTokenScalarFieldEnum | Prisma.RefreshTokenScalarFieldEnum[]
|
||||
}
|
||||
|
||||
/**
|
||||
* User without action
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Controller, Get, Query } from '@nestjs/common'
|
||||
import { ApiQuery } from '@nestjs/swagger'
|
||||
import { CardexService } from './cardex.service'
|
||||
|
||||
@Controller('cardex')
|
||||
export class CardexController {
|
||||
constructor(private readonly service: CardexService) {}
|
||||
|
||||
@Get('')
|
||||
@ApiQuery({ name: 'startDate', required: true, type: Date })
|
||||
@ApiQuery({ name: 'endDate', required: false, type: Date })
|
||||
@ApiQuery({ name: 'inventoryId', required: false, type: Number })
|
||||
@ApiQuery({ name: 'productId', required: false, type: Number })
|
||||
async getStock(
|
||||
@Query('startDate') startDate: Date,
|
||||
@Query('endDate') endDate: Date,
|
||||
@Query('inventoryId') inventoryId: number,
|
||||
@Query('productId') productId: number,
|
||||
) {
|
||||
return this.service.getCardex({
|
||||
startDate,
|
||||
endDate,
|
||||
inventoryId,
|
||||
productId,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../../prisma/prisma.module'
|
||||
import { CardexController } from './cardex.controller'
|
||||
import { CardexService } from './cardex.service'
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [CardexController],
|
||||
providers: [CardexService],
|
||||
})
|
||||
export class CardexModule {}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import dayjs from 'dayjs'
|
||||
import { ResponseMapper } from '../../common/response/response-mapper'
|
||||
import { Prisma } from '../../generated/prisma/client'
|
||||
import { PrismaService } from '../../prisma/prisma.service'
|
||||
import { ReadCardexDto } from './dto/read-pos.dto'
|
||||
|
||||
@Injectable()
|
||||
export class CardexService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async getCardex({ startDate, endDate, inventoryId, productId }: ReadCardexDto) {
|
||||
if (!endDate) {
|
||||
endDate = dayjs(startDate).add(1, 'months').toDate()
|
||||
}
|
||||
const where: Prisma.StockMovementWhereInput = {}
|
||||
|
||||
if (inventoryId !== undefined) where.inventoryId = inventoryId
|
||||
if (productId !== undefined) where.productId = productId
|
||||
|
||||
if (startDate || endDate) {
|
||||
where.createdAt = {}
|
||||
if (startDate) where.createdAt.gte = new Date(startDate)
|
||||
if (endDate) where.createdAt.lte = new Date(endDate)
|
||||
}
|
||||
|
||||
const items = await this.prisma.stockMovement.findMany({
|
||||
where,
|
||||
include: { product: true, inventory: true },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
})
|
||||
|
||||
const mapped = items.map(i => ({
|
||||
id: i.id,
|
||||
type: i.type,
|
||||
quantity: Number(i.quantity),
|
||||
fee: Number(i.fee),
|
||||
totalCost: Number(i.totalCost),
|
||||
referenceType: i.referenceType,
|
||||
referenceId: i.referenceId,
|
||||
createdAt: i.createdAt,
|
||||
avgCost: Number(i.avgCost),
|
||||
product: {
|
||||
name: i.product.name,
|
||||
sku: i.product.sku,
|
||||
id: i.productId,
|
||||
},
|
||||
inventory: {
|
||||
name: i.inventory.name,
|
||||
id: i.inventoryId,
|
||||
},
|
||||
}))
|
||||
|
||||
return ResponseMapper.list(mapped)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger'
|
||||
import { IsDateString, IsNumber, IsOptional } from 'class-validator'
|
||||
|
||||
export class ReadCardexDto {
|
||||
@ApiPropertyOptional()
|
||||
@IsDateString()
|
||||
startDate: Date
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
endDate?: Date
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
inventoryId?: number
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
productId?: number
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { PrismaModule } from '../prisma/prisma.module'
|
||||
import { PrismaModule } from '../../prisma/prisma.module'
|
||||
import { PosController } from './pos.controller'
|
||||
import { PosService } from './pos.service'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { ResponseMapper } from '../common/response/response-mapper'
|
||||
import { Prisma } from '../generated/prisma/client'
|
||||
import { PrismaService } from '../prisma/prisma.service'
|
||||
import { ResponseMapper } from '../../common/response/response-mapper'
|
||||
import { Prisma } from '../../generated/prisma/client'
|
||||
import { PrismaService } from '../../prisma/prisma.service'
|
||||
import { CreateOrderDto } from './dto/create-pos.dto'
|
||||
|
||||
@Injectable()
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common'
|
||||
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common'
|
||||
import { ApiQuery } from '@nestjs/swagger'
|
||||
import { CreateProductDto } from './dto/create-product.dto'
|
||||
import { UpdateProductDto } from './dto/update-product.dto'
|
||||
import { ProductsService } from './products.service'
|
||||
@@ -13,8 +14,10 @@ export class ProductsController {
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.productsService.findAll()
|
||||
@ApiQuery({ name: 'page', required: false, type: Number, default: 1 })
|
||||
@ApiQuery({ name: 'pageSize', required: false, type: Number, default: 30 })
|
||||
findAll(@Query('page') page: number = 1, @Query('pageSize') pageSize: number = 30) {
|
||||
return this.productsService.findAll(page, pageSize)
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { ShortEntity } from '../common/interfaces/response-models'
|
||||
import { ResponseMapper } from '../common/response/response-mapper'
|
||||
import { Prisma } from '../generated/prisma/client'
|
||||
import { PrismaService } from '../prisma/prisma.service'
|
||||
import { CreateProductDto } from './dto/create-product.dto'
|
||||
|
||||
@@ -28,10 +29,17 @@ export class ProductsService {
|
||||
return ResponseMapper.create(item)
|
||||
}
|
||||
|
||||
async findAll() {
|
||||
const items = await this.prisma.product.findMany({
|
||||
include: { brand: true, category: true, stockBalances: true },
|
||||
})
|
||||
async findAll(page = 1, pageSize = 20) {
|
||||
const query: Prisma.ProductFindManyArgs = {}
|
||||
|
||||
const [items, count] = await this.prisma.$transaction([
|
||||
this.prisma.product.findMany({
|
||||
include: { brand: true, category: true, stockBalances: true },
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
}),
|
||||
this.prisma.product.count(),
|
||||
])
|
||||
|
||||
const mapped = items.map(p => {
|
||||
const { brandId, categoryId, ...rest } = p as any
|
||||
@@ -48,7 +56,7 @@ export class ProductsService {
|
||||
|
||||
return { ...rest, brand, category, stock }
|
||||
})
|
||||
return ResponseMapper.list(mapped)
|
||||
return ResponseMapper.paginate(mapped, count, page, pageSize)
|
||||
}
|
||||
|
||||
async findOne(id: number) {
|
||||
|
||||
@@ -23,8 +23,8 @@ export class PurchaseReceiptItemsService {
|
||||
const item = await this.prisma.purchaseReceiptItem.create({ data: payload })
|
||||
return ResponseMapper.create(item)
|
||||
}
|
||||
async findAll(receiptId?: number) {
|
||||
const where = receiptId ? { receiptId: Number(receiptId) } : undefined
|
||||
async findAll(receiptId: number) {
|
||||
const where = { receiptId: Number(receiptId) }
|
||||
const items = await this.prisma.purchaseReceiptItem.findMany({
|
||||
where,
|
||||
include: { product: true },
|
||||
|
||||
@@ -47,7 +47,22 @@ export class PurchaseReceiptsService {
|
||||
async findOne(id: number) {
|
||||
const item = await this.prisma.purchaseReceipt.findUnique({
|
||||
where: { id },
|
||||
include: { supplier: true, inventory: true, items: true },
|
||||
include: {
|
||||
supplier: {
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
},
|
||||
},
|
||||
inventory: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
items: true,
|
||||
},
|
||||
})
|
||||
if (!item) return null
|
||||
return ResponseMapper.single(item)
|
||||
|
||||
@@ -17,9 +17,22 @@ export class SuppliersController {
|
||||
return this.suppliersService.findAll()
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.suppliersService.findOne(Number(id))
|
||||
@Get(':supplierId')
|
||||
findOne(@Param('supplierId') supplierId: string) {
|
||||
return this.suppliersService.findOne(Number(supplierId))
|
||||
}
|
||||
|
||||
@Get(':supplierId/invoices')
|
||||
getInvoices(@Param('supplierId') supplierId: string) {
|
||||
return this.suppliersService.getInvoices(Number(supplierId))
|
||||
}
|
||||
|
||||
@Get(':supplierId/invoices/:invoiceId')
|
||||
getInvoice(
|
||||
@Param('supplierId') supplierId: string,
|
||||
@Param('invoiceId') invoiceId: string,
|
||||
) {
|
||||
return this.suppliersService.getInvoice(Number(supplierId), Number(invoiceId))
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
|
||||
@@ -18,7 +18,82 @@ export class SuppliersService {
|
||||
async findOne(id: number) {
|
||||
const item = await this.prisma.supplier.findUnique({ where: { id } })
|
||||
if (!item) return null
|
||||
return ResponseMapper.single(item)
|
||||
const receiptsInfo = await this.prisma.purchaseReceipt.aggregate({
|
||||
where: {
|
||||
supplierId: id,
|
||||
},
|
||||
_count: true,
|
||||
_sum: {
|
||||
totalAmount: true,
|
||||
},
|
||||
})
|
||||
|
||||
return ResponseMapper.single({
|
||||
...item,
|
||||
receiptsOverview: {
|
||||
totalPayment: receiptsInfo._sum.totalAmount,
|
||||
count: receiptsInfo._count,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async getInvoices(supplierId: number) {
|
||||
const items = await this.prisma.purchaseReceipt.findMany({
|
||||
where: {
|
||||
supplierId,
|
||||
},
|
||||
include: {
|
||||
inventory: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
items: {
|
||||
select: {
|
||||
id: true,
|
||||
count: true,
|
||||
fee: true,
|
||||
total: true,
|
||||
product: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
sku: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
omit: {
|
||||
inventoryId: true,
|
||||
supplierId: true,
|
||||
},
|
||||
})
|
||||
return ResponseMapper.list(items)
|
||||
}
|
||||
|
||||
async getInvoice(supplierId: number, invoiceId: number) {
|
||||
const invoice = await this.prisma.purchaseReceipt.findUnique({
|
||||
where: {
|
||||
id: invoiceId,
|
||||
supplierId,
|
||||
},
|
||||
include: {
|
||||
inventory: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
items: true,
|
||||
},
|
||||
omit: {
|
||||
inventoryId: true,
|
||||
supplierId: true,
|
||||
},
|
||||
})
|
||||
return ResponseMapper.single(invoice)
|
||||
}
|
||||
|
||||
async update(id: number, data: any) {
|
||||
|
||||
Reference in New Issue
Block a user