[598-50][@ant] feat: докладка у пайщика — готовый акт и одна кнопка «Подписать», без «Принять»
Оператор в окне выдачи («Со склада») теперь получает АПП-выдачи по строкам корзины и подписывает их первой подписью (signiss1) сразу при формировании докладки (getStockIssuancePayloads + createStockProposal с подписанными актами). Пайщику докладка приходит в гейт готовым актом (пункты имущества, кол-во, цена, две суммы: с членского / конвертация с паевого). Одна кнопка «Подписать» (+«Отказаться»): подписывает Заявление о конвертации при дефиците и контрподписывает signiss2 по строкам — finalizeStockIssuance создаёт заказы и выдаёт имущество. Кнопка «Принять предложение» убрана: принятие = подпись акта. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -159,19 +159,37 @@ export async function cancelStockProposal(proposal_id: string): Promise<Marketpl
|
||||
return result as MarketplaceStockProposalView;
|
||||
}
|
||||
|
||||
/** Строка корзины докладки для подготовки актов оператору (offer_id + quantity). */
|
||||
export type StockIssuancePayloadInput = Queries.Marketplace.StockIssuancePayloads.IInput['data'];
|
||||
/** Строка к подписи оператором: order_hash + АПП-выдачи (signiss1_document). */
|
||||
export type IStockIssuanceOperatorLine =
|
||||
Queries.Marketplace.StockIssuancePayloads.IOutput['marketplaceStockIssuancePayloads'][number];
|
||||
|
||||
/**
|
||||
* Нагрузка к принятию предложения со склада: строки-заказы + ОДНО Заявление о
|
||||
* конвертации на весь дефицит (convert_document пустой — членских средств
|
||||
* хватает, при замене из высвобожденных подписывать нечего).
|
||||
* Нагрузка к ОДНОЙ подписи пайщика: по строке — order_hash и подписанный
|
||||
* оператором АПП-выдачи (signiss1_aggregate, для контрподписи получения), плюс
|
||||
* ОДНО Заявление о конвертации на весь дефицит (convert_document пустой —
|
||||
* членских средств хватает, подписывать нужно только сами акты).
|
||||
*/
|
||||
export type IStockProposalAcceptPayload =
|
||||
Queries.Marketplace.StockProposalSignablePayloads.IOutput['marketplaceStockProposalSignablePayloads'];
|
||||
|
||||
export type IStockAcceptInput = Mutations.Marketplace.AcceptStockProposal.IInput['data'];
|
||||
/** Строка-заказ принятия (offer_id + order_hash). */
|
||||
export type IStockAcceptOrderLine = IStockAcceptInput['order_lines'][number];
|
||||
export type IStockFinalizeInput = Mutations.Marketplace.FinalizeStockIssuance.IInput['data'];
|
||||
/** Строка финализации (offer_id + контрподписанный пайщиком signiss2-акт). */
|
||||
export type IStockFinalizeOrderLine = IStockFinalizeInput['order_lines'][number];
|
||||
/** Подписанное единое Заявление о конвертации (если был дефицит). */
|
||||
export type IStockConvertSigned = NonNullable<IStockAcceptInput['signed_convert']>;
|
||||
export type IStockConvertSigned = NonNullable<IStockFinalizeInput['signed_convert']>;
|
||||
|
||||
/** Акты приёма-передачи к подписи оператором при формировании докладки. */
|
||||
export async function getStockIssuancePayloads(
|
||||
data: StockIssuancePayloadInput,
|
||||
): Promise<IStockIssuanceOperatorLine[]> {
|
||||
const { [Queries.Marketplace.StockIssuancePayloads.name]: result } = await client.Query(
|
||||
Queries.Marketplace.StockIssuancePayloads.query,
|
||||
{ variables: { data } },
|
||||
);
|
||||
return result as IStockIssuanceOperatorLine[];
|
||||
}
|
||||
|
||||
export async function getStockProposalSignablePayloads(
|
||||
proposal_id: string,
|
||||
@@ -184,14 +202,22 @@ export async function getStockProposalSignablePayloads(
|
||||
return result as IStockProposalAcceptPayload;
|
||||
}
|
||||
|
||||
export async function acceptStockProposal(
|
||||
/**
|
||||
* Пайщик одной подписью утверждает докладку: по строкам — контрподписанные
|
||||
* signiss2-акты, при дефиците — единое подписанное Заявление о конвертации.
|
||||
*/
|
||||
export async function finalizeStockIssuance(
|
||||
proposal_id: string,
|
||||
order_lines: IStockAcceptOrderLine[],
|
||||
order_lines: IStockFinalizeOrderLine[],
|
||||
signed_convert?: IStockConvertSigned | null,
|
||||
): Promise<{ proposal: MarketplaceStockProposalView; order_ids: string[] }> {
|
||||
const data: IStockAcceptInput = { proposal_id, order_lines, signed_convert: signed_convert ?? null };
|
||||
const { [Mutations.Marketplace.AcceptStockProposal.name]: result } = await client.Mutation(
|
||||
Mutations.Marketplace.AcceptStockProposal.mutation,
|
||||
const data: IStockFinalizeInput = {
|
||||
proposal_id,
|
||||
order_lines,
|
||||
signed_convert: signed_convert ?? null,
|
||||
};
|
||||
const { [Mutations.Marketplace.FinalizeStockIssuance.name]: result } = await client.Mutation(
|
||||
Mutations.Marketplace.FinalizeStockIssuance.mutation,
|
||||
{ variables: { data } },
|
||||
);
|
||||
return result as { proposal: MarketplaceStockProposalView; order_ids: string[] };
|
||||
|
||||
+24
-3
@@ -16,7 +16,9 @@ import { marketplaceUnitShort } from 'src/shared/lib/consts/marketplace-units';
|
||||
import {
|
||||
getChairmanSignablePayload,
|
||||
openIssuance,
|
||||
getStockIssuancePayloads,
|
||||
createStockProposal,
|
||||
type CreateStockProposalInput,
|
||||
type MarketplaceOrderIssuanceView,
|
||||
} from '../api';
|
||||
import StockPickDialog, { type StockPickLine } from './StockPickDialog.vue';
|
||||
@@ -354,15 +356,34 @@ async function confirm(): Promise<void> {
|
||||
}),
|
||||
);
|
||||
|
||||
// Докладка со склада: создаём предложение тем же действием — пайщику оно
|
||||
// уйдёт в гейт вместе с заказом (принятие и подпись получения — у пайщика).
|
||||
// Докладка со склада: оператор формирует бандл и СРАЗУ подписывает акты
|
||||
// приёма-передачи первой подписью (signiss1) — пайщику докладка уйдёт в гейт
|
||||
// готовым актом с одной кнопкой «Подписать» (получение), без отдельного
|
||||
// «Принять». Заказ из остатка и сама выдача рождаются на подписи пайщика.
|
||||
if (restockLines.value.length && recipientAccount.value && issueBraname.value) {
|
||||
try {
|
||||
await createStockProposal({
|
||||
const payloads = await getStockIssuancePayloads({
|
||||
braname: issueBraname.value,
|
||||
member_account: recipientAccount.value,
|
||||
items: restockLines.value.map((l) => ({ offer_id: l.offer_id, quantity: l.quantity })),
|
||||
});
|
||||
const items = await Promise.all(
|
||||
payloads.map(async (p) => ({
|
||||
offer_id: p.offer_id,
|
||||
quantity: p.quantity,
|
||||
order_hash: p.order_hash,
|
||||
signiss1_act: (await docSigner.signDocument(
|
||||
p.signiss1_document,
|
||||
globalStore.username,
|
||||
1,
|
||||
)) as CreateStockProposalInput['items'][number]['signiss1_act'],
|
||||
})),
|
||||
);
|
||||
await createStockProposal({
|
||||
braname: issueBraname.value,
|
||||
member_account: recipientAccount.value,
|
||||
items,
|
||||
});
|
||||
restockLines.value = [];
|
||||
} catch (e) {
|
||||
FailAlert(e, 'Не удалось доложить со склада');
|
||||
|
||||
+30
-14
@@ -12,12 +12,13 @@ import {
|
||||
listMyReadyToReceive,
|
||||
finalizeOrdererIssuance,
|
||||
listStockProposals,
|
||||
acceptStockProposal,
|
||||
finalizeStockIssuance,
|
||||
declineStockProposal,
|
||||
getStockProposalSignablePayloads,
|
||||
type MarketplaceOrderIssuanceView,
|
||||
type MarketplaceStockProposalView,
|
||||
type IStockConvertSigned,
|
||||
type IStockFinalizeOrderLine,
|
||||
} from 'src/pages/Marketplace/OperatorIssuance/api';
|
||||
|
||||
/**
|
||||
@@ -240,12 +241,13 @@ async function signOrderer(task: OrdererPickupTask): Promise<void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Пайщик принимает предложение со склада: по строкам создаются заказы (средства
|
||||
* резервируются на акцепте — при нехватке паевых средств backend вернёт
|
||||
* человеческую ошибку), и следом оператор открывает выдачу — акт придёт в этот
|
||||
* же гейт обычной задачей заказчика.
|
||||
* Пайщик ОДНОЙ подписью утверждает докладку как акт получения. Подписывает:
|
||||
* при дефиците членских — единое Заявление о конвертации (paевой → членский),
|
||||
* затем по каждой строке контрподписывает АПП-выдачи (signiss2) поверх подписи
|
||||
* оператора. Backend создаёт заказы из остатка и проводит выдачу — имущество
|
||||
* выдаётся сразу. Никакого отдельного «Принять»: принятие = подпись акта.
|
||||
*/
|
||||
async function acceptProposal(task: MarketplaceStockProposalView): Promise<void> {
|
||||
async function signProposal(task: MarketplaceStockProposalView): Promise<void> {
|
||||
const global = useGlobalStore();
|
||||
const wifKey = global.wif?.toString();
|
||||
if (!wifKey) {
|
||||
@@ -254,23 +256,37 @@ async function acceptProposal(task: MarketplaceStockProposalView): Promise<void>
|
||||
}
|
||||
signingKey.value = task.id;
|
||||
try {
|
||||
// Одно Заявление о конвертации на весь дефицит сверх членских средств; если
|
||||
// средств хватает (замена из высвобожденных — «возьмите вот это»), документа
|
||||
// нет и подписывать пайщику нечего.
|
||||
const payload = await getStockProposalSignablePayloads(task.id);
|
||||
const signer = new Classes.Document(wifKey);
|
||||
|
||||
// 1. Заявление о конвертации — только при дефиците членских средств (иначе
|
||||
// convert_document пустой и подписывать нечего, кроме самих актов).
|
||||
let signed_convert: IStockConvertSigned | null = null;
|
||||
if (payload.convert_document) {
|
||||
const signer = new Classes.Document(wifKey);
|
||||
signed_convert = (await signer.signDocument(
|
||||
payload.convert_document,
|
||||
global.username,
|
||||
1,
|
||||
)) as IStockConvertSigned;
|
||||
}
|
||||
const { order_ids } = await acceptStockProposal(task.id, payload.order_lines, signed_convert);
|
||||
SuccessAlert(
|
||||
`Предложение принято: оформлено позиций со склада — ${order_ids.length}. Оператор откроет выдачу.`,
|
||||
|
||||
// 2. Контрподпись получения (signiss2) поверх подписи оператора по каждой
|
||||
// строке — документ НЕ перегенерируется, берётся агрегат rawDocument +
|
||||
// document с подписью оператора (канон 2-подписи).
|
||||
const order_lines: IStockFinalizeOrderLine[] = await Promise.all(
|
||||
payload.order_lines.map(async (line) => ({
|
||||
offer_id: line.offer_id,
|
||||
signed_signiss2_act: (await signer.signDocument(
|
||||
line.signiss1_aggregate.rawDocument,
|
||||
global.username,
|
||||
2,
|
||||
[line.signiss1_aggregate.document],
|
||||
)) as IStockFinalizeOrderLine['signed_signiss2_act'],
|
||||
})),
|
||||
);
|
||||
|
||||
const { order_ids } = await finalizeStockIssuance(task.id, order_lines, signed_convert);
|
||||
SuccessAlert(`Имущество получено по ${order_ids.length} позиц. со склада. Акт подписан.`);
|
||||
} catch (error) {
|
||||
FailAlert(error);
|
||||
} finally {
|
||||
@@ -304,7 +320,7 @@ export function useOnsiteSignatureGate() {
|
||||
refresh,
|
||||
signSupplier,
|
||||
signOrderer,
|
||||
acceptProposal,
|
||||
signProposal,
|
||||
declineProposal,
|
||||
};
|
||||
}
|
||||
|
||||
+9
-9
@@ -30,7 +30,7 @@ const {
|
||||
refresh,
|
||||
signSupplier,
|
||||
signOrderer,
|
||||
acceptProposal,
|
||||
signProposal,
|
||||
declineProposal,
|
||||
} = useOnsiteSignatureGate();
|
||||
|
||||
@@ -180,15 +180,15 @@ BaseDialog(
|
||||
q-icon(name='draw', size='18px')
|
||||
| Подписать и получить
|
||||
|
||||
//- Докладка: оператор предложил имущество со склада кооператива — пайщик
|
||||
//- решает на месте. На принятии средства резервируются и акт придёт сюда же.
|
||||
//- Докладка со склада кооператива: оператор уже подписал акт передачи —
|
||||
//- пайщику остаётся одна подпись получения (принятие = подпись акта).
|
||||
BaseCard.onsite-gate__card(v-for='p in proposalTasks', :key='p.id')
|
||||
template(#head)
|
||||
.onsite-gate__head
|
||||
q-icon(name='add_shopping_cart', size='28px')
|
||||
q-icon(name='inventory_2', size='28px')
|
||||
.onsite-gate__ident
|
||||
span.onsite-gate__name Предложение со склада кооператива
|
||||
span.onsite-gate__sub Оператор пункта выдачи предлагает добавить к получению
|
||||
span.onsite-gate__name Получение со склада кооператива
|
||||
span.onsite-gate__sub Подтвердите получение имущества — ваша подпись акта
|
||||
|
||||
table.onsite-gate__table
|
||||
thead
|
||||
@@ -225,11 +225,11 @@ BaseDialog(
|
||||
variant='primary',
|
||||
:loading='proposalBusy(p.id)',
|
||||
:disabled='anySigning && !proposalBusy(p.id)',
|
||||
@click='acceptProposal(p)'
|
||||
@click='signProposal(p)'
|
||||
)
|
||||
template(#icon-left)
|
||||
q-icon(name='check', size='18px')
|
||||
| Принять предложение
|
||||
q-icon(name='draw', size='18px')
|
||||
| Подписать
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
Reference in New Issue
Block a user