WIP CCC Part Price Change.
This commit is contained in:
@@ -6,6 +6,7 @@ import http from "http";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import ImportJob from "../decoder/decoder";
|
||||
import folderScan from "../decoder/folder-scan";
|
||||
import { handlePartsPariceChangeRequest } from "../ppc/ppc-handler";
|
||||
import { handleQuickBookRequest } from "../quickbooks-desktop/quickbooks-desktop";
|
||||
|
||||
export default class LocalServer {
|
||||
@@ -117,6 +118,7 @@ export default class LocalServer {
|
||||
res.status(200).json(files);
|
||||
return;
|
||||
});
|
||||
this.app.post("/ppc", handlePartsPariceChangeRequest);
|
||||
this.app.post(
|
||||
"/import",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
|
||||
@@ -7,6 +7,8 @@ import ImportJob from "../decoder/decoder";
|
||||
import store from "../store/store";
|
||||
import { StartWatcher, StopWatcher } from "../watcher/watcher";
|
||||
import {
|
||||
SettingsPpcFilPathGet,
|
||||
SettingsPpcFilPathSet,
|
||||
SettingsWatchedFilePathsAdd,
|
||||
SettingsWatchedFilePathsGet,
|
||||
SettingsWatchedFilePathsRemove,
|
||||
@@ -93,6 +95,10 @@ ipcMain.handle(
|
||||
ipcTypes.toMain.settings.watcher.setpolling,
|
||||
SettingsWatcherPollingSet,
|
||||
);
|
||||
|
||||
ipcMain.handle(ipcTypes.toMain.settings.getPpcFilePath, SettingsPpcFilPathGet);
|
||||
ipcMain.handle(ipcTypes.toMain.settings.setPpcFilePath, SettingsPpcFilPathSet);
|
||||
|
||||
ipcMain.handle(ipcTypes.toMain.user.getActiveShop, () => {
|
||||
return store.get("app.bodyshop.shopname");
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { BrowserWindow, dialog, IpcMainInvokeEvent } from "electron";
|
||||
import { dialog, IpcMainInvokeEvent } from "electron";
|
||||
import log from "electron-log/main";
|
||||
import _ from "lodash";
|
||||
import Store from "../store/store";
|
||||
import { getMainWindow } from "../util/toRenderer";
|
||||
import {
|
||||
addWatcherPath,
|
||||
removeWatcherPath,
|
||||
@@ -10,7 +11,7 @@ import {
|
||||
} from "../watcher/watcher";
|
||||
|
||||
const SettingsWatchedFilePathsAdd = async (): Promise<string[]> => {
|
||||
const mainWindow = BrowserWindow.getAllWindows()[0]; //TODO: Filter to only main window once a proper key has been set.
|
||||
const mainWindow = getMainWindow();
|
||||
if (!mainWindow) {
|
||||
log.error("No main window found when trying to open dialog");
|
||||
return [];
|
||||
@@ -74,8 +75,30 @@ const SettingsWatcherPollingSet = async (
|
||||
|
||||
return { enabled, interval };
|
||||
};
|
||||
const SettingsPpcFilPathGet = async (): Promise<string> => {
|
||||
const ppcFilePath: string = Store.get("settings.ppcFilePath");
|
||||
return ppcFilePath;
|
||||
};
|
||||
const SettingsPpcFilPathSet = async (): Promise<string> => {
|
||||
const mainWindow = getMainWindow();
|
||||
if (!mainWindow) {
|
||||
log.error("No main window found when trying to open dialog");
|
||||
return "";
|
||||
}
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ["openDirectory"],
|
||||
});
|
||||
|
||||
if (!result.canceled) {
|
||||
Store.set("settings.ppcFilePath", result.filePaths[0]); //There should only ever be on directory that was selected.
|
||||
}
|
||||
|
||||
return (Store.get("settings.ppcFilePath") as string) || "";
|
||||
};
|
||||
|
||||
export {
|
||||
SettingsPpcFilPathGet,
|
||||
SettingsPpcFilPathSet,
|
||||
SettingsWatchedFilePathsAdd,
|
||||
SettingsWatchedFilePathsGet,
|
||||
SettingsWatchedFilePathsRemove,
|
||||
|
||||
12
src/main/ppc/ppc-generate-file.ts
Normal file
12
src/main/ppc/ppc-generate-file.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import path from "path";
|
||||
import store from "../store/store";
|
||||
|
||||
const generatePpcFilePath = (filename: string): string => {
|
||||
const ppcOutFilePath: string | null = store.get("settings.ppcFilePath");
|
||||
if (!ppcOutFilePath) {
|
||||
throw new Error("PPC file path is not set");
|
||||
}
|
||||
return path.resolve(ppcOutFilePath, filename);
|
||||
};
|
||||
|
||||
export { generatePpcFilePath };
|
||||
299
src/main/ppc/ppc-generate-lin.ts
Normal file
299
src/main/ppc/ppc-generate-lin.ts
Normal file
@@ -0,0 +1,299 @@
|
||||
import { DBFFile, FieldDescriptor } from "dbffile";
|
||||
import { generatePpcFilePath } from "./ppc-generate-file";
|
||||
import { PpcJob } from "./ppc-handler";
|
||||
|
||||
const GenerateLinFile = async (job: PpcJob): Promise<boolean> => {
|
||||
const records = job.joblines.map((line) => {
|
||||
return {
|
||||
//TODO: There are missing types here. May require server side updates, but we are missing things like LINE_NO, LINE_IND, etc.
|
||||
TRAN_CODE: 2,
|
||||
UNQ_SEQ: line.unq_seq,
|
||||
ACT_PRICE: line.act_price,
|
||||
};
|
||||
});
|
||||
|
||||
let dbf = await DBFFile.create(
|
||||
generatePpcFilePath(`${job.ciecaid}.LIN`),
|
||||
linFieldDescriptors,
|
||||
);
|
||||
console.log("DBF file created.");
|
||||
await dbf.appendRecords(records);
|
||||
console.log(`${records.length} LIN file records added.`);
|
||||
return true;
|
||||
};
|
||||
|
||||
export default GenerateLinFile;
|
||||
|
||||
//Taken from a set of CCC ems files.
|
||||
const linFieldDescriptors: FieldDescriptor[] = [
|
||||
{
|
||||
name: "LINE_NO",
|
||||
type: "N",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LINE_IND",
|
||||
type: "C",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LINE_REF",
|
||||
type: "N",
|
||||
size: 3,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TRAN_CODE",
|
||||
type: "C",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "DB_REF",
|
||||
type: "C",
|
||||
size: 7,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "UNQ_SEQ",
|
||||
type: "N",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "WHO_PAYS",
|
||||
type: "C",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LINE_DESC",
|
||||
type: "C",
|
||||
size: 40,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PART_TYPE",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PART_DES_J",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "GLASS_FLAG",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "OEM_PARTNO",
|
||||
type: "C",
|
||||
size: 25,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PRICE_INC",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ALT_PART_I",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "TAX_PART",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "DB_PRICE",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "ACT_PRICE",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "PRICE_J",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "CERT_PART",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PART_QTY",
|
||||
type: "N",
|
||||
size: 2,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ALT_CO_ID",
|
||||
type: "C",
|
||||
size: 20,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ALT_PARTNO",
|
||||
type: "C",
|
||||
size: 25,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ALT_OVERRD",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "ALT_PARTM",
|
||||
type: "C",
|
||||
size: 45,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PRT_DSMK_P",
|
||||
type: "N",
|
||||
size: 7,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "PRT_DSMK_M",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "MOD_LBR_TY",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "DB_HRS",
|
||||
type: "N",
|
||||
size: 5,
|
||||
decimalPlaces: 1,
|
||||
},
|
||||
{
|
||||
name: "MOD_LB_HRS",
|
||||
type: "N",
|
||||
size: 5,
|
||||
decimalPlaces: 1,
|
||||
},
|
||||
{
|
||||
name: "LBR_INC",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_OP",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_HRS_J",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_TYP_J",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_OP_J",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PAINT_STG",
|
||||
type: "N",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "PAINT_TONE",
|
||||
type: "N",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_TAX",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "LBR_AMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "MISC_AMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "MISC_SUBLT",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "MISC_TAX",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "BETT_TYPE",
|
||||
type: "C",
|
||||
size: 4,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
{
|
||||
name: "BETT_PCTG",
|
||||
type: "N",
|
||||
size: 8,
|
||||
decimalPlaces: 4,
|
||||
},
|
||||
{
|
||||
name: "BETT_AMT",
|
||||
type: "N",
|
||||
size: 9,
|
||||
decimalPlaces: 2,
|
||||
},
|
||||
{
|
||||
name: "BETT_TAX",
|
||||
type: "L",
|
||||
size: 1,
|
||||
decimalPlaces: 0,
|
||||
},
|
||||
];
|
||||
71
src/main/ppc/ppc-handler.ts
Normal file
71
src/main/ppc/ppc-handler.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { UUID } from "crypto";
|
||||
import log from "electron-log/main";
|
||||
import express from "express";
|
||||
import fs from "fs";
|
||||
import _ from "lodash";
|
||||
import path from "path";
|
||||
import errorTypeCheck from "../../util/errorTypeCheck";
|
||||
import store from "../store/store";
|
||||
import GenerateLinFile from "./ppc-generate-lin";
|
||||
|
||||
const handlePartsPariceChangeRequest = async (
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
): Promise<void> => {
|
||||
//Route handler here only.
|
||||
|
||||
const { job } = req.body as { job: PpcJob };
|
||||
try {
|
||||
await generatePartsPriceChange(job);
|
||||
} catch (error) {
|
||||
log.error("Error generating parts price change", errorTypeCheck(error));
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: "Error generating parts price change.",
|
||||
...errorTypeCheck(error),
|
||||
});
|
||||
return;
|
||||
}
|
||||
res.status(200).json({ success: true });
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
const generatePartsPriceChange = async (job: PpcJob): Promise<void> => {
|
||||
log.debug(" Generating parts price change");
|
||||
//Check to make sure that the PPC Output file path exists. If it doesn't, create it. If it's not set, abandon ship.
|
||||
|
||||
const ppcOutFilePath: string | null = store.get("settings.ppcFilePath");
|
||||
if (_.isEmpty(ppcOutFilePath) || ppcOutFilePath === null) {
|
||||
log.error("PPC file path is not set");
|
||||
throw new Error("PPC file path is not set");
|
||||
}
|
||||
|
||||
//If the directory doesn't exist, create it.
|
||||
const directoryPath = path.dirname(ppcOutFilePath);
|
||||
if (!fs.existsSync(directoryPath)) {
|
||||
log.info(`Directory does not exist. Creating: ${directoryPath}`);
|
||||
fs.mkdirSync(directoryPath, { recursive: true });
|
||||
}
|
||||
const LinFile = await GenerateLinFile(job);
|
||||
|
||||
//Generate the required file envelop to be sent back to CCC.
|
||||
};
|
||||
|
||||
export interface PpcJob {
|
||||
id: UUID;
|
||||
ciecaid: string;
|
||||
ro_number: string;
|
||||
joblines: {
|
||||
removed: boolean;
|
||||
act_price_before_ppc: number | null;
|
||||
id: string;
|
||||
act_price: number;
|
||||
unq_seq: string; //TODO: Might be a number.
|
||||
}[];
|
||||
bodyshop: {
|
||||
timezone: string;
|
||||
};
|
||||
}
|
||||
|
||||
export { handlePartsPariceChangeRequest };
|
||||
@@ -5,6 +5,7 @@ const store = new Store({
|
||||
settings: {
|
||||
runOnStartup: true,
|
||||
filepaths: [],
|
||||
ppcFilePath: null,
|
||||
qbFilePath: "",
|
||||
runWatcherOnStartup: true,
|
||||
polling: {
|
||||
|
||||
@@ -21,10 +21,7 @@ const NavigationHeader: React.FC = () => {
|
||||
];
|
||||
const isTest = window.api.isTest();
|
||||
return (
|
||||
<Badge.Ribbon
|
||||
text={isTest && "Connected to Test Environment"}
|
||||
color={isTest && "red"}
|
||||
>
|
||||
<Badge.Ribbon text={isTest && "TEST ENV"} color={isTest && "red"}>
|
||||
<Layout.Header style={{ display: "flex", alignItems: "center" }}>
|
||||
<Menu
|
||||
theme="dark"
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { FolderOpenFilled } from "@ant-design/icons";
|
||||
import { Button, Card, Input, Space } from "antd";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ipcTypes from "../../../../util/ipcTypes.json";
|
||||
|
||||
const SettingsPpcFilepath: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [ppcFilePath, setPpcFilePath] = useState<string | null>(null);
|
||||
|
||||
const getPollingStateFromStore = (): void => {
|
||||
window.electron.ipcRenderer
|
||||
.invoke(ipcTypes.toMain.settings.getPpcFilePath)
|
||||
.then((filePath: string | null) => {
|
||||
setPpcFilePath(filePath);
|
||||
});
|
||||
};
|
||||
|
||||
//Get state first time it renders.
|
||||
useEffect(() => {
|
||||
getPollingStateFromStore();
|
||||
}, []);
|
||||
|
||||
const handlePathChange = (): void => {
|
||||
window.electron.ipcRenderer
|
||||
.invoke(ipcTypes.toMain.settings.setPpcFilePath)
|
||||
.then((filePath: string | null) => {
|
||||
setPpcFilePath(filePath);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card title={t("settings.labels.ppcfilepath")}>
|
||||
<Space wrap>
|
||||
<Input
|
||||
value={ppcFilePath || ""}
|
||||
placeholder={t("settings.labels.ppcfilepath")}
|
||||
disabled
|
||||
/>
|
||||
<Button onClick={handlePathChange} icon={<FolderOpenFilled />} />
|
||||
</Space>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
export default SettingsPpcFilepath;
|
||||
@@ -2,6 +2,7 @@ import { Col, Row } from "antd";
|
||||
import SettingsWatchedPaths from "./Settings.WatchedPaths";
|
||||
import SettingsWatcher from "./Settings.Watcher";
|
||||
import Welcome from "../Welcome/Welcome";
|
||||
import SettingsPpcFilepath from "./Settings.PpcFilePath";
|
||||
|
||||
const Settings: React.FC = () => {
|
||||
console.log("is test?", window.api.isTest());
|
||||
@@ -16,6 +17,9 @@ const Settings: React.FC = () => {
|
||||
<Col span={12}>
|
||||
<SettingsWatcher />
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<SettingsPpcFilepath />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"add": "toMain_settings_filepaths_add",
|
||||
"remove": "toMain_settings_filepaths_remove"
|
||||
},
|
||||
"getPpcFilePath": "toMain_settings_filepaths_getPpcFilePath",
|
||||
"setPpcFilePath": "toMain_settings_filepaths_setPpcFilePath",
|
||||
"watcher": {
|
||||
"getpolling": "toMain_settings_watcher_getpolling",
|
||||
"setpolling": "toMain_settings_watcher_setpolling"
|
||||
|
||||
@@ -1,45 +1,46 @@
|
||||
{
|
||||
"translation": {
|
||||
"auth": {
|
||||
"labels": {
|
||||
"welcome": "Hi {{name}}"
|
||||
},
|
||||
"login": {
|
||||
"error": "The username and password combination provided is not valid.",
|
||||
"login": "Log In",
|
||||
"resetpassword": "Reset Password"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"errorboundary": "Uh oh - we've hit an error.",
|
||||
"notificationtitle": "Error Encountered"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Home",
|
||||
"settings": "Settings",
|
||||
"signout": "Sign Out"
|
||||
},
|
||||
"settings": {
|
||||
"actions": {
|
||||
"addpath": "Add path",
|
||||
"startwatcher": "Start Watcher",
|
||||
"stopwatcher": "Stop Watcher\n"
|
||||
},
|
||||
"labels": {
|
||||
"pollinginterval": "Polling Interval (ms)",
|
||||
"started": "Started",
|
||||
"stopped": "Stopped",
|
||||
"watchedpaths": "Watched Paths",
|
||||
"watchermodepolling": "Polling",
|
||||
"watchermoderealtime": "Real Time",
|
||||
"watcherstatus": "Watcher Status"
|
||||
}
|
||||
},
|
||||
"updates": {
|
||||
"apply": "Apply Update",
|
||||
"available": "An update is available.",
|
||||
"download": "Download Update",
|
||||
"downloading": "An update is downloading."
|
||||
}
|
||||
}
|
||||
"translation": {
|
||||
"auth": {
|
||||
"labels": {
|
||||
"welcome": "Hi {{name}}"
|
||||
},
|
||||
"login": {
|
||||
"error": "The username and password combination provided is not valid.",
|
||||
"login": "Log In",
|
||||
"resetpassword": "Reset Password"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"errorboundary": "Uh oh - we've hit an error.",
|
||||
"notificationtitle": "Error Encountered"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Home",
|
||||
"settings": "Settings",
|
||||
"signout": "Sign Out"
|
||||
},
|
||||
"settings": {
|
||||
"actions": {
|
||||
"addpath": "Add path",
|
||||
"startwatcher": "Start Watcher",
|
||||
"stopwatcher": "Stop Watcher\n"
|
||||
},
|
||||
"labels": {
|
||||
"pollinginterval": "Polling Interval (ms)",
|
||||
"ppcfilepath": "Parts Price Change File Path",
|
||||
"started": "Started",
|
||||
"stopped": "Stopped",
|
||||
"watchedpaths": "Watched Paths",
|
||||
"watchermodepolling": "Polling",
|
||||
"watchermoderealtime": "Real Time",
|
||||
"watcherstatus": "Watcher Status"
|
||||
}
|
||||
},
|
||||
"updates": {
|
||||
"apply": "Apply Update",
|
||||
"available": "An update is available.",
|
||||
"download": "Download Update",
|
||||
"downloading": "An update is downloading."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,6 +250,19 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>ppcfilepath</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>started</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
|
||||
Reference in New Issue
Block a user